xref: /illumos-gate/usr/src/lib/libslp/javalib/com/sun/slp/ServiceStoreInMemory.java (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2001,2003 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  */
26 
27 //  ServiceStoreInMemory.java: An in-memory implementation
28 //			       of the service store.
29 //  Author:           James Kempf
30 //  Created On:       Mon Oct 20 12:36:35 1997
31 //  Last Modified By: James Kempf
32 //  Last Modified On: Tue Mar  2 15:32:23 1999
33 //  Update Count:     472
34 //
35 
36 package com.sun.slp;
37 
38 import java.util.*;
39 import java.io.*;
40 
41 /**
42  * The ServiceStoreInMemory class implements the ServiceStore interface
43  * on in-memory data structures.
44  * <details of those structures here>
45  *
46  * @author James Kempf
47  */
48 
49 class ServiceStoreInMemory extends Object implements ServiceStore {
50 
51     /**
52      * The BVCollector interface allows various
53      * data structures to collect stuff from the BtreeVector.
54      *
55      * @author James Kempf
56      */
57 
58     private interface BVCollector {
59 
60 	// Set the return value.
61 
62 	abstract void setReturn(ServiceRecordInMemory rec);
63 
64     }
65 
66     /**
67      * The ParserBVCollector class implements a BtreeVector
68      * collector for the parser.
69      *
70      * @author James Kempf
71      */
72 
73     private class ParserBVCollector extends Object implements BVCollector {
74 
75 	Parser.ParserRecord prReturns = null;
76 	private Vector scopes = null;
77 
78 	ParserBVCollector(Vector scopes) {
79 	    this.scopes = scopes;
80 
81 	}
82 
83 	public void setReturn(ServiceRecordInMemory rec) {
84 
85 	    Hashtable services = prReturns.services;
86 	    Hashtable signatures = prReturns.signatures;
87 	    ServiceURL surl = rec.getServiceURL();
88 
89 	    // Add if we don't already have it.
90 
91 	    if (services.get(surl) == null) {
92 		Vector s = (Vector)rec.getScopes().clone();
93 
94 		DATable.filterScopes(s, scopes, false);
95 
96 		// Need to adjust lifetime to reflect the time to live. Don't
97 		//  set the lifetime if it has already expired.
98 
99 		long lifetime =
100 		    (rec.getExpirationTime() -
101 		     System.currentTimeMillis()) / 1000;
102 
103 		if (lifetime > 0) {
104 		    ServiceURL url =
105 			new ServiceURL(surl.toString(), (int)lifetime);
106 
107 		    services.put(surl, s);
108 
109 		    Hashtable sig = rec.getURLSignature();
110 
111 		    if (sig != null) {
112 			signatures.put(url, sig);
113 
114 		    }
115 		}
116 	    }
117 	}
118     }
119 
120     /**
121      * The AttributeBVCollector class implements a BtreeVector
122      * collector for the collecting attribute values by type.
123      *
124      * @author James Kempf
125      */
126 
127     private class AttributeBVCollector extends Object implements BVCollector {
128 
129 	private Hashtable alreadySeen = new Hashtable();
130 						// records already seen.
131 	private Vector attrTags = null;	// tags to match against records
132 	private Hashtable ht = new Hashtable();	// for collecting attributes.
133 	private Vector ret = null;		// for returns.
134 
135 	AttributeBVCollector(Vector attrTags, Vector ret) {
136 	    this.attrTags = attrTags;
137 	    this.ret = ret;
138 
139 	}
140 
141 	public void setReturn(ServiceRecordInMemory rec) {
142 
143 	    // If we've got it already, then don't add again.
144 
145 	    if (alreadySeen.get(rec) == null) {
146 		alreadySeen.put(rec, rec);
147 
148 		try {
149 		    findMatchingAttributes(rec, attrTags, ht, ret);
150 
151 		} catch (ServiceLocationException ex) {
152 
153 		    Assert.slpassert(false,
154 				  "ssim_attrbvc_botch",
155 				  new Object[] {ex.getMessage()});
156 		}
157 	    }
158 	}
159     }
160 
161     /**
162      * The ScopeBVCollector class implements a BtreeVector
163      * collector for the collecting records if scopes match.
164      *
165      * @author James Kempf
166      */
167 
168     private class ScopeBVCollector extends Object implements BVCollector {
169 
170 	private Hashtable alreadySeen = new Hashtable();
171 						// for those we've seen
172 	private Vector records = null;		// for returns.
173 	private Vector scopes = null;		// the scopes we're looking for
174 
175 	ScopeBVCollector(Vector records, Vector scopes) {
176 	    this.records = records;
177 	    this.scopes = scopes;
178 
179 	}
180 
181 	public void setReturn(ServiceRecordInMemory rec) {
182 
183 	    // If we've got it already, then don't add.
184 
185 	    if (alreadySeen.get(rec) == null) {
186 		alreadySeen.put(rec, rec);
187 
188 		if (scopes == null) {
189 		    records.addElement(rec);
190 
191 		} else {
192 
193 		    // Check scopes.
194 
195 		    int i;
196 		    Vector rscopes = rec.getScopes();
197 		    int len = scopes.size();
198 
199 		    for (i = 0; i < len; i++) {
200 			if (rscopes.contains(scopes.elementAt(i))) {
201 			    records.addElement(rec);
202 			    break;
203 
204 			}
205 		    }
206 		}
207 	    }
208 	}
209     }
210 
211     /**
212      * The AllBVCollector class implements a BtreeVector
213      * collector for collecting all records.
214      *
215      * @author James Kempf
216      */
217 
218     private class AllBVCollector extends Object implements BVCollector {
219 
220 	private Vector records = null;			// for returns.
221 
222 	AllBVCollector(Vector records) {
223 	    this.records = records;
224 
225 	}
226 
227 	public void setReturn(ServiceRecordInMemory rec) {
228 
229 	    // If we've got it already, then don't add.
230 
231 	    if (!records.contains(rec)) {
232 		records.addElement(rec);
233 
234 	    }
235 	}
236     }
237 
238     /**
239      * The List class implements a linked list for storing records
240      * in the BtreeVector structure.
241      *
242      * @author James Kempf
243      */
244 
245     private class List extends Object {
246 
247 	ServiceRecordInMemory record = null;
248 	List next = null;
249 	List prev = null;
250 
251 	// Create a new list object.
252 
253 	List(ServiceRecordInMemory record) {
254 	    this.record = record;
255 
256 	}
257 
258 	// Insert a new record after this one. Return the new
259 	//  record.
260 
261 	synchronized List insertAfter(ServiceRecordInMemory record) {
262 	    List newRec = new List(record);
263 	    newRec.next = next;
264 	    newRec.prev = this;
265 
266 	    if (next != null) {
267 		next.prev = newRec;
268 
269 	    }
270 
271 	    this.next = newRec;
272 
273 	    return newRec;
274 
275 	}
276 
277 	// Delete this record from the list.
278 
279 	synchronized void delete() {
280 
281 	    if (next != null) {
282 		next.prev = prev;
283 	    }
284 
285 	    if (prev != null) {
286 		prev.next = next;
287 
288 	    }
289 
290 	    prev = null;
291 	    next = null;
292 
293 	}
294     }
295 
296     /**
297      * The RegRecord class implements a record with the value for the
298      * record buckets. It is used as elements in BtreeVector.
299      *
300      * @author James Kempf
301      */
302 
303     private class RegRecord extends Object {
304 
305 	Object value = null;		// the value for these registrations.
306 	List head = new List(null); 	// head of the list always null,
307 				        //  never changes.
308 	// Construct a new one.
309 
310 	RegRecord(Object value) {
311 	    this.value = value;
312 
313 	}
314 
315 	// Add a new record to the buckets, return new element.
316 
317 	List add(ServiceRecordInMemory rec) {
318 
319 	    return head.insertAfter(rec);
320 
321 	}
322 
323 	// For every element in record's list, set the return value in the
324 	// returns object. Since deletions may have removed everything
325 	// from this record, return true only if something was there.
326 
327 	boolean setReturn(BVCollector returns) {
328 
329 	    boolean match = false;
330 	    List l = head;
331 
332 	    for (l = l.next; l != null; l = l.next) {
333 		ServiceRecordInMemory rec = l.record;
334 		returns.setReturn(rec);
335 		match = true;
336 
337 	    }
338 
339 	    return match;
340 	}
341 
342 	public String toString() {
343 	    return "<RegRecord value="+value+"list="+head.next+">";
344 
345 	}
346     }
347 
348     /**
349      * The BtreeVector class stores registrations in sorted order. The
350      * Quicksort algorithm is used to insert items and search for something.
351      *
352      * @author James Kempf
353      */
354 
355     private class BtreeVector extends Object {
356 
357 	// Contains the sorted vector.
358 
359 	private Vector contents = new Vector();
360 
361 	public String toString() {
362 	    return "<BtreeVector "+contents.toString()+">";
363 
364 	}
365 
366 	// Return the contents as a sorted vector of RegRecord.
367 	//  Note that this doesn't return a copy, so
368 	//  the vector can be side-effected.
369 
370 	Vector getContents() {
371 	    return contents;
372 
373 	}
374 
375 	// Add the entire contents of the vector to the return record.
376 
377 	boolean getAll(BVCollector returns) {
378 
379 	    int i, n = contents.size();
380 	    boolean match = false;
381 
382 	    for (i = 0; i < n; i++) {
383 		RegRecord rec = (RegRecord)contents.elementAt(i);
384 
385 		match = match | rec.setReturn(returns);
386 	    }
387 
388 	    return match;
389 	}
390 
391 	// Add a new record to this vector. We also garbage collect any
392 	// records that are empty. Return the list object added.
393 
394 	List add(Object value, ServiceRecordInMemory record) {
395 	    RegRecord rec = walkVector(value, true);  // an update...
396 
397 	    // Add the record to this one.
398 
399 	    return rec.add(record);
400 
401 	}
402 
403 
404 	// Add only if no element in the vector matches the tag.
405 
406 	boolean matchDoesNotContain(Object pattern, BVCollector returns) {
407 
408 	    // Go through the vector, putting in anything that isn't equal.
409 
410 	    int i, n = contents.size();
411 	    Vector noMatch = new Vector();
412 	    boolean match = false;
413 
414 	    for (i = 0; i < n; i++) {
415 		RegRecord rec = (RegRecord)contents.elementAt(i);
416 
417 		if (!compareEqual(rec.value, pattern)) {
418 
419 		    // Add to prospective returns.
420 
421 		    noMatch.addElement(rec);
422 
423 		}
424 
425 	    }
426 
427 	    // If we got this far, there are some no matches.
428 
429 	    n = noMatch.size();
430 
431 	    for (i = 0; i < n; i++) {
432 		RegRecord rec = (RegRecord)noMatch.elementAt(i);
433 
434 		match = match | rec.setReturn(returns);
435 
436 	    }
437 
438 	    return match;
439 
440 	}
441 
442 	boolean
443 	    matchEqual(Object pattern, BVCollector returns) {
444 
445 	    boolean match = false;
446 
447 	    // We can't walk the vector if the value is an AttributePattern,
448 	    //  because equals doesn't apply.
449 
450 	    if (pattern instanceof AttributePattern) {
451 		int i, n = contents.size();
452 
453 		for (i = 0; i < n; i++) {
454 		    RegRecord rec = (RegRecord)contents.elementAt(i);
455 		    AttributeString val = (AttributeString)rec.value;
456 		    AttributePattern pat = (AttributePattern)pattern;
457 
458 		    if (pat.match(val)) {
459 			match = match | rec.setReturn(returns);
460 
461 		    }
462 		}
463 	    } else {
464 		RegRecord rec = walkVector(pattern, false);
465 							// not an update...
466 
467 		// If nothing came back, return false.
468 
469 		if (rec == null) {
470 		    match = false;
471 
472 		} else {
473 
474 		    // Otherwise set returns in the vector.
475 
476 		    match = rec.setReturn(returns);
477 
478 		}
479 	    }
480 
481 	    return match;
482 	}
483 
484 	boolean
485 	    matchNotEqual(Object pattern, BVCollector returns) {
486 
487 	    // Go through the vector, putting in anything that isn't equal.
488 
489 	    int i, n = contents.size();
490 	    boolean match = false;
491 
492 	    for (i = 0; i < n; i++) {
493 		RegRecord rec = (RegRecord)contents.elementAt(i);
494 
495 		if (!compareEqual(rec.value, pattern)) {
496 		    match = match | rec.setReturn(returns);
497 
498 		}
499 	    }
500 
501 	    return match;
502 	}
503 
504 	boolean
505 	    matchLessEqual(Object pattern,
506 			   BVCollector returns) {
507 
508 	    // Go through the vector, putting in anything that is
509 	    // less than or equal.
510 
511 	    int i, n = contents.size();
512 	    boolean match = false;
513 
514 	    for (i = 0; i < n; i++) {
515 		RegRecord rec = (RegRecord)contents.elementAt(i);
516 
517 		if (!compareLessEqual(rec.value, pattern)) {
518 		    break;
519 
520 		}
521 
522 		match = match | rec.setReturn(returns);
523 	    }
524 
525 	    return match;
526 	}
527 
528 	boolean
529 	    matchNotLessEqual(Object pattern,
530 			      BVCollector returns) {
531 	    // Go through the vector, putting in anything that is not
532 	    // less than or equal. Start at the top.
533 
534 	    int i, n = contents.size();
535 	    boolean match = false;
536 
537 	    for (i = n - 1; i >= 0; i--) {
538 		RegRecord rec = (RegRecord)contents.elementAt(i);
539 
540 		if (compareLessEqual(rec.value, pattern)) {
541 		    break;
542 
543 		}
544 
545 		match = match | rec.setReturn(returns);
546 	    }
547 
548 	    return match;
549 	}
550 
551 	boolean
552 	    matchGreaterEqual(Object pattern,
553 			      BVCollector returns) {
554 	    // Go through the vector, putting in anything that is greater
555 	    // than or equal. Start at the top.
556 
557 	    int i, n = contents.size();
558 	    boolean match = false;
559 
560 	    for (i = n - 1; i >= 0; i--) {
561 		RegRecord rec = (RegRecord)contents.elementAt(i);
562 
563 		if (!compareGreaterEqual(rec.value, pattern)) {
564 		    break;
565 
566 		}
567 
568 		match = match | rec.setReturn(returns);
569 	    }
570 
571 	    return match;
572 	}
573 
574 	boolean
575 	    matchNotGreaterEqual(Object pattern,
576 				 BVCollector returns) {
577 	    // Go through the vector, putting in anything that is not
578 	    // than or equal.
579 
580 	    int i, n = contents.size();
581 	    boolean match = false;
582 
583 	    for (i = 0; i < n; i++) {
584 		RegRecord rec = (RegRecord)contents.elementAt(i);
585 
586 		if (compareGreaterEqual(rec.value, pattern)) {
587 		    break;
588 
589 		}
590 
591 		match = match | rec.setReturn(returns);
592 	    }
593 
594 	    return match;
595 	}
596 
597 	// Binary tree walk the vector, performing the operation. Note that
598 	//  we use dynamic typing heavily here to get maximum code reuse.
599 
600 	private RegRecord
601 	    walkVector(Object pattern, boolean update) {
602 
603 	    // Get the starting set of indicies.
604 
605 	    int size = contents.size();
606 	    int middle = size / 2;
607 	    int top = size - 1;
608 	    int bottom = 0;
609 	    RegRecord rec = null;
610 
611 	    top = (top < 0 ? 0:top);
612 
613 	    while (size > 0) {
614 
615 		// Get the one at the current middle.
616 
617 		rec = (RegRecord)contents.elementAt(middle);
618 
619 		// Garbage Collection.
620 		//  If it was null, then delete. But only if we're
621 		//  inserting. We leave it alone on lookup.
622 
623 		if (update) {
624 		    if (rec.head.next == null) {
625 
626 			contents.removeElementAt(middle);
627 
628 			size = size - 1;
629 			middle = bottom + (size / 2);
630 			top = top - 1;
631 
632 			top = (top < 0 ? 0:top);
633 
634 			continue;
635 		    }
636 		}
637 
638 		// Compare value to record, if equal, return record.
639 		//  code.
640 
641 		if (compareEqual(rec.value, pattern)) {
642 		    return rec;
643 
644 		} else if (compareLessEqual(pattern, rec.value)) {
645 
646 		    // Recalculate index. We move left, because the value is
647 		    // less that the value in the vector, so an equal value
648 		    // must be to the left. Note that the top is not in the
649 		    // interval because it has already been checked and
650 		    // found wanting.
651 
652 		    top = middle;
653 		    size = (top - bottom);
654 		    middle = top - (size / 2);
655 		    middle = (middle < 0 ? 0:middle);
656 
657 		    if (middle == top) {
658 
659 			// Neither top nor middle are in the interval,
660 			// so size is zero. We need to compare with bottom.
661 
662 			rec = null;
663 			RegRecord trec = (RegRecord)contents.elementAt(bottom);
664 
665 			if (update) {
666 			    rec = new RegRecord(pattern);
667 
668 			    // If the pattern is equal to bottom, return it.
669 			    // If the pattern is less than or equal to bottom,
670 			    // we insert it at bottom. If it is greater
671 			    // than or equal, we insert it at middle.
672 
673 			    if (compareEqual(trec.value, pattern)) {
674 				return trec;
675 
676 			    } else if (compareLessEqual(pattern, trec.value)) {
677 
678 				// Pattern is less than bottom, so insert
679 				// at bottom.
680 
681 				contents.insertElementAt(rec, bottom);
682 
683 			    } else {
684 				contents.insertElementAt(rec, middle);
685 
686 			    }
687 			} else {
688 
689 			    // If it equals bottom, then return bottom rec.
690 
691 			    if (compareEqual(trec.value, pattern)) {
692 				rec = trec;
693 
694 			    }
695 			}
696 
697 			break;
698 
699 		    }
700 
701 		} else if (compareGreaterEqual(pattern, rec.value)) {
702 
703 		    // Recalculate index. We move right, because the value is
704 		    // greater that the value in the vector, so an equal
705 		    // value must be to the right. Note that the top is not
706 		    // in the interval because it has already been checked
707 		    // and found wanting.
708 
709 		    bottom = middle;
710 		    size = (top - bottom);
711 		    middle = bottom + (size / 2);
712 
713 		    if (middle == bottom) {
714 
715 			// Neither bottom nor middle is in the interval,
716 			// so size is zero. We need to compare with top.
717 
718 			rec = null;
719 			RegRecord trec = (RegRecord)contents.elementAt(top);
720 
721 			if (update) {
722 			    rec = new RegRecord(pattern);
723 
724 			    // If the pattern is equal to the top, we
725 			    // return the top. If the pattern is greater
726 			    // then top, we insert it after top, else we
727 			    // insert it at top.
728 
729 			    if (compareEqual(trec.value, pattern)) {
730 				return trec;
731 
732 			    } else if (compareGreaterEqual(pattern,
733 							   trec.value)) {
734 
735 				// Pattern is greater than top, so insert
736 				// after top.
737 
738 				int i = top + 1;
739 
740 				if (i >= contents.size()) {
741 				    contents.addElement(rec);
742 
743 				} else {
744 				    contents.insertElementAt(rec, i);
745 
746 				}
747 			    } else {
748 
749 				// Pattern is less than top, so insert at
750 				// top, causing top to move up.
751 
752 				contents.insertElementAt(rec, top);
753 
754 			    }
755 			} else {
756 
757 			    // If it equals top, then return top rec.
758 
759 			    if (compareEqual(trec.value, pattern)) {
760 				rec = trec;
761 
762 			    }
763 			}
764 
765 			break;
766 
767 		    }
768 		}
769 	    }
770 
771 	    // Take care of update where vector is empty or cleaned out.
772 
773 	    if (update && rec == null) {
774 		rec = new RegRecord(pattern);
775 
776 		Assert.slpassert((contents.size() == 0),
777 			      "ssim_btree_botch",
778 			      new Object[0]);
779 
780 		contents.addElement(rec);
781 	    }
782 
783 	    return rec;
784 	}
785 
786 	// Add any registrations that match the pattern.
787 
788 	boolean
789 	    compareEqual(Object target, Object pattern) {
790 
791 	    if (target instanceof Integer ||
792 		target instanceof Boolean ||
793 		target instanceof Opaque ||
794 		target instanceof Long) {
795 		if (pattern.equals(target)) {
796 		    return true;
797 
798 		}
799 
800 	    } else if (target instanceof AttributeString) {
801 
802 		// If the pattern is an AttributePattern instead of an
803 		// AttributeString, the subclass method will get invoked.
804 
805 		if (((AttributeString)pattern).match(
806 						(AttributeString)target)) {
807 		    return true;
808 
809 		}
810 
811 	    } else {
812 		Assert.slpassert(false,
813 			      "ssim_unk_qtype",
814 			      new Object[] {pattern.getClass().getName()});
815 	    }
816 
817 	    return false;
818 
819 	}
820 
821 	// Add any registrations that are less than or equal to the pattern.
822 
823 	boolean
824 	    compareLessEqual(Object target, Object pattern) {
825 
826 	    if (target instanceof Integer) {
827 		if (((Integer)target).intValue() <=
828 		    ((Integer)pattern).intValue()) {
829 		    return true;
830 
831 		}
832 
833 	    } else if (target instanceof AttributeString) {
834 
835 		if (((AttributeString)target).lessEqual(
836 						(AttributeString)pattern)) {
837 		    return true;
838 
839 		}
840 
841 	    } else if (target instanceof Long) {
842 		if (((Long)target).longValue() <=
843 		    ((Long)pattern).longValue()) {
844 		    return true;
845 
846 		}
847 
848 	    } else if (target instanceof Boolean ||
849 		       target instanceof Opaque) {
850 		if (target.toString().compareTo(pattern.toString()) <= 0) {
851 		    return true;
852 
853 		}
854 	    } else {
855 		Assert.slpassert(false,
856 			      "ssim_unk_qtype",
857 			      new Object[] {target.getClass().getName()});
858 	    }
859 
860 	    return false;
861 
862 	}
863 
864 	// Add any registrations that are greater than or equal to the pattern.
865 
866 	boolean
867 	    compareGreaterEqual(Object target, Object pattern) {
868 
869 	    if (target instanceof Integer) {
870 		if (((Integer)target).intValue() >=
871 		    ((Integer)pattern).intValue()) {
872 		    return true;
873 
874 		}
875 
876 	    } else if (target instanceof AttributeString) {
877 
878 		if (((AttributeString)target).greaterEqual(
879 						(AttributeString)pattern)) {
880 		    return true;
881 
882 		}
883 
884 	    } else if (target instanceof Long) {
885 		if (((Long)target).longValue() >=
886 		    ((Long)pattern).longValue()) {
887 		    return true;
888 
889 		}
890 
891 	    } else if (target instanceof Boolean ||
892 		       target instanceof Opaque) {
893 		if (target.toString().compareTo(pattern.toString()) >= 0) {
894 		    return true;
895 
896 		}
897 
898 	    } else {
899 		Assert.slpassert(false,
900 			      "ssim_unk_qtype",
901 			      new Object[] {target.getClass().getName()});
902 	    }
903 
904 	    return false;
905 
906 	}
907     }
908 
909     /**
910      * The InMemoryEvaluator evaluates queries for ServiceStoreInMemory.
911      *
912      * @author James Kempf
913      */
914 
915     private class InMemoryEvaluator implements Parser.QueryEvaluator {
916 
917 	private Hashtable attrLevel;	// Sorted attribute table.
918 	private BtreeVector attrLevelNot;   // Used for universal negation.
919 	private Vector inScopes;            // Input scopes.
920 	private ParserBVCollector returns;  // For gathering results.
921 
922 	InMemoryEvaluator(Hashtable ht,
923 			  BtreeVector btv,
924 			  Vector nscopes) {
925 	    attrLevel = ht;
926 	    attrLevelNot = btv;
927 	    inScopes = nscopes;
928 	    returns = new ParserBVCollector(inScopes);
929 
930 
931 	}
932 
933 	// Evaluate the query by matching the attribute tag and
934 	//  value, using the operator. If invert is true, then
935 	//  return records that do NOT match.
936 
937 	public boolean
938 	    evaluate(AttributeString tag,
939 		     char op,
940 		     Object pattern,
941 		     boolean invert,
942 		     Parser.ParserRecord prReturns)
943 	    throws ServiceLocationException {
944 
945 	    boolean match = false;
946 	    returns.prReturns = prReturns;
947 
948 	    // If inversion is on, then gather all from the
949 	    //  table of registrations that do NOT have this
950 	    //  attribute.
951 
952 	    if (invert) {
953 		match = attrLevelNot.matchDoesNotContain(tag, returns);
954 
955 	    }
956 
957 	    // Find the table of classes v.s. sorted value vectors.
958 
959 	    Hashtable ttable = (Hashtable)attrLevel.get(tag);
960 
961 	    // If attribute not present, then simply return.
962 
963 	    if (ttable == null) {
964 
965 		return match;
966 
967 	    }
968 
969 	    // If operator is present, then return all.
970 
971 	    if (op == Parser.PRESENT) {
972 
973 		// ...but only if invert isn't on.
974 
975 		if (!invert) {
976 
977 		    // We use attrLevelNot to get all, because it
978 		    //  will also pick up keywords. There are
979 		    //  no keywords in attrLevel because keywords
980 		    //  don't have any values.
981 
982 		    match = attrLevelNot.matchEqual(tag, returns);
983 
984 		}
985 
986 		return match;
987 	    }
988 
989 	    // We know that the type table is fully initialized with
990 	    //  BtreeVectors for each type.
991 
992 	    // Get the pattern's class. Pattern will not be null because
993 	    //  the parser has checked for it and PRESENT has been
994 	    //  filtered out above.
995 
996 	    Class pclass = pattern.getClass();
997 	    String typeKey = pclass.getName();
998 
999 	    // If the class is AttributePattern, then use AttributeString
1000 	    //  instead.
1001 
1002 	    if (pattern instanceof AttributePattern) {
1003 		typeKey = pclass.getSuperclass().getName();
1004 
1005 	    }
1006 
1007 	    // If invert is on, collect those whose types don't match as
1008 	    //  well.
1009 
1010 	    if (invert) {
1011 		Enumeration en = ttable.keys();
1012 
1013 		while (en.hasMoreElements()) {
1014 		    String key = (String)en.nextElement();
1015 
1016 		    // Only record if the type does NOT match.
1017 
1018 		    if (!key.equals(typeKey)) {
1019 			BtreeVector bvec = (BtreeVector)ttable.get(key);
1020 
1021 			match = match | bvec.getAll(returns);
1022 
1023 		    }
1024 		}
1025 	    }
1026 
1027 	    // Get the sorted value vector corresponding to the value class.
1028 
1029 	    BtreeVector bvec = (BtreeVector)ttable.get(typeKey);
1030 
1031 	    // Do the appropriate thing for the operator.
1032 
1033 	    switch (op) {
1034 
1035 	    case Parser.EQUAL:
1036 
1037 		if (!invert) {
1038 		    match = bvec.matchEqual(pattern, returns);
1039 
1040 		} else {
1041 		    match = bvec.matchNotEqual(pattern, returns);
1042 
1043 		}
1044 		break;
1045 
1046 	    case Parser.LESS:
1047 
1048 		// Note that we've filtered out Opaque, Boolean, and wildcarded
1049 		// strings before calling this method.
1050 
1051 		if (!invert) {
1052 		    match = bvec.matchLessEqual(pattern, returns);
1053 
1054 		} else {
1055 		    match = bvec.matchNotLessEqual(pattern, returns);
1056 
1057 		}
1058 		break;
1059 
1060 	    case Parser.GREATER:
1061 
1062 		// Note that we've filtered out Opaque and Boolean
1063 		// before calling this method.
1064 
1065 		if (!invert) {
1066 		    match = bvec.matchGreaterEqual(pattern, returns);
1067 
1068 		} else {
1069 		    match = bvec.matchNotGreaterEqual(pattern, returns);
1070 
1071 		}
1072 		break;
1073 
1074 	    default:
1075 		Assert.slpassert(false,
1076 			      "ssim_unk_qop",
1077 			      new Object[] {new Character((char)op)});
1078 	    }
1079 
1080 	    return match;
1081 	}
1082     }
1083 
1084     /**
1085      * The ServiceRecordInMemory class implements the
1086      * ServiceStore.ServiceRecord interface on in-memory data structures.
1087      * Each property is implemented as an instance variable.
1088      *
1089      * @author James Kempf
1090      */
1091 
1092     private class ServiceRecordInMemory extends Object
1093 	implements ServiceStore.ServiceRecord {
1094 
1095 	private ServiceURL serviceURL = null;	// the service URL
1096 	private Vector attrList = null;		// the attribute list
1097 	private Locale locale = null;		// the locale
1098 	private long timeToDie = 0;		// when the record should die.
1099 	private Vector scopes = null;		// the scopes
1100 	private Hashtable urlSig = null;
1101 				// URL signature block list, if any.
1102 	private Hashtable attrSig = null;
1103 				// Attribute signature block list, if any.
1104 
1105 	// Create a ServiceStoreInMemory record.
1106 
1107 	ServiceRecordInMemory(ServiceURL surl, Vector alist,
1108 			      Vector nscopes, Locale loc,
1109 			      Hashtable nurlSig,
1110 			      Hashtable nattrSig) {
1111 
1112 	    // All need to be nonnull.
1113 
1114 	    Assert.nonNullParameter(surl, "surl");
1115 	    Assert.nonNullParameter(alist, "alist");
1116 	    Assert.nonNullParameter(nscopes, "nscopes");
1117 	    Assert.nonNullParameter(loc, "loc");
1118 
1119 	    serviceURL = surl;
1120 	    attrList = attributeVectorToServerAttribute(alist, loc);
1121 	    scopes = nscopes;
1122 	    locale = loc;
1123 	    urlSig = nurlSig;
1124 	    attrSig = nattrSig;
1125 
1126 	    int lifetime = serviceURL.getLifetime();
1127 
1128 	    timeToDie = lifetime * 1000 + System.currentTimeMillis();
1129 	}
1130 
1131 	/**
1132 	 * Return the ServiceURL for the record.
1133 	 *
1134 	 * @return The record's service URL.
1135 	 */
1136 
1137 	public final ServiceURL getServiceURL() {
1138 	    return serviceURL;
1139 
1140 	}
1141 
1142 	/**
1143 	 * Return the Vector of ServerAttribute objects for the record.
1144 	 *
1145 	 * @return Vector of ServerAttribute objects for the record.
1146 	 */
1147 
1148 	public final Vector getAttrList() {
1149 	    return attrList;
1150 
1151 	}
1152 
1153 	/**
1154 	 * Return the locale of the registration.
1155 	 *
1156 	 * @return The locale of the registration.
1157 	 */
1158 
1159 	public final Locale getLocale() {
1160 	    return locale;
1161 
1162 	}
1163 
1164 	/**
1165 	 * Return the Vector of scopes in which the record is registered.
1166 	 *
1167 	 * @return Vector of strings with scope names.
1168 	 */
1169 
1170 	public final Vector getScopes() {
1171 	    return scopes;
1172 
1173 	}
1174 
1175 	/**
1176 	 * Return the expiration time for the record. This informs the
1177 	 * service store when the record should expire and be removed
1178 	 * from the table.
1179 	 *
1180 	 * @return The expiration time for the record.
1181 	 */
1182 
1183 	public long getExpirationTime() {
1184 	    return timeToDie;
1185 
1186 	}
1187 
1188 	/**
1189 	 * Return the URL signature list.
1190 	 *
1191 	 * @return URL signature block list.
1192 	 */
1193 
1194 	public Hashtable getURLSignature() {
1195 	    return urlSig;
1196 
1197 	}
1198 
1199 	/**
1200 	 * Return the attribute signature list.
1201 	 *
1202 	 * @return Attribute signature list.
1203 	 */
1204 
1205 	public Hashtable getAttrSignature() {
1206 	    return attrSig;
1207 
1208 	}
1209 
1210 
1211 	//
1212 	// Package-local methods.
1213 
1214 	final void setAttrList(Vector newList) {
1215 	    attrList = newList;
1216 
1217 	}
1218 
1219 	final void setScopes(Vector newScopes) {
1220 	    scopes = newScopes;
1221 
1222 	}
1223 
1224 	final void setURLSignature(Hashtable nauth) {
1225 	    urlSig = nauth;
1226 
1227 	}
1228 
1229 	final void setAttrSignature(Hashtable nauth) {
1230 	    attrSig = nauth;
1231 
1232 	}
1233 
1234 	public String toString() {
1235 
1236 	    String ret = "{";
1237 
1238 	    ret +=
1239 		serviceURL + ", " + locale + ", " + attrList + ", " +
1240 		scopes + ", " + locale + ", " + urlSig + ", " + attrSig;
1241 
1242 	    ret += "}";
1243 
1244 	    return ret;
1245 	}
1246 
1247 	// Convert a vector of ServiceLocationAttribute objects to
1248 	// ServerAttibutes.
1249 
1250 	private Vector
1251 	    attributeVectorToServerAttribute(Vector attrs, Locale locale) {
1252 	    int i, n = attrs.size();
1253 	    Vector v = new Vector();
1254 
1255 	    for (i = 0; i < n; i++) {
1256 		ServiceLocationAttribute attr =
1257 		    (ServiceLocationAttribute)attrs.elementAt(i);
1258 
1259 		v.addElement(new ServerAttribute(attr, locale));
1260 	    }
1261 
1262 	    return v;
1263 	}
1264 
1265     }
1266 
1267     /**
1268      * A record for scopeTypeLangTable table,
1269      *
1270      * @author James Kempf
1271      */
1272 
1273     private class STLRecord extends Object {
1274 
1275 	Hashtable attrValueSort = new Hashtable();
1276 				// Table of attributes, sorted by value.
1277 	BtreeVector attrSort = new BtreeVector();	// Btree of attributes.
1278 	boolean isAbstract = false;
1279 				// True if the record is for an abstract
1280 				//  type.
1281 	STLRecord(boolean isAbstract) {
1282 	    this.isAbstract = isAbstract;
1283 
1284 	}
1285     }
1286 
1287     //
1288     // ServiceStoreInMemory instance variables.
1289     //
1290 
1291     // ServiceStoreInMemory maintains an invaraint that the record for a
1292     //  particular URL, set of scopes, and locale is the same object
1293     //  (pointer-wise) regardless of where it is inserted into the table.
1294     //  So it can be compared with ==.
1295 
1296     // The scopeTypeLangTable
1297     //
1298     //  Keys for this table are scope/service type/lang tag. Values are
1299     //  STLRecord objects. The STLRecord.attrValueSort field is a Hashtable
1300     //  where all registrations *having* the attribute tag keys in the
1301     //  table are contained. This table is used in queries for positive
1302     //  logical expressions. The STLRecord.attrSort field is a BtreeVector
1303     //  keyed by attribute. It is used for negative queries to find all
1304     //  records not having a particular attribute and to find all
1305     //  registrations. The STLRecord.isAbstract field tells whether the record
1306     //  is for an abstract type name.
1307     //
1308     //  The values in the STLRecord.attrValueSort hashtable are themselves
1309     //  hashtables. These hashtables are keyed by one of the type keys below,
1310     //  with  the values being BtreeVector objects. The BtreeVector objects
1311     //  contain sorted lists of RegRecord objects for Integer,
1312     //  AttributeString, Boolean, and Opaque types. All records having
1313     //  values equal to the value in the RegRecord are put into a list
1314     //  on the RegRecord. There is no STLRecord.attrValueSort
1315     //  hashtable for keyword attributes because they have no values.
1316     //  The parser evaluator must use the STLRecord.attrSort hashtable when a
1317     //  present operator is encountered (the only valid operator with a
1318     //  keyword).
1319     //
1320     //  The values in the STLRecord.attrSort BtreeVector are RegRecord
1321     //  objects with all records having that attribute tag being on the
1322     //  RegRecord list.
1323 
1324     // Keys for the various types.
1325 
1326     private final static String INTEGER_TYPE = "java.lang.Integer";
1327     private final static String ATTRIBUTE_STRING_TYPE =
1328 	"com.sun.slp.AttributeString";
1329     private final static String BOOLEAN_TYPE = "java.lang.Boolean";
1330     private final static String OPAQUE_TYPE = "com.sun.slp.Opaque";
1331 
1332     private Hashtable scopeTypeLangTable = new Hashtable();
1333 
1334     //  The urlScopeLangTable
1335     //
1336     //  Keys for this table are service url as a string. We don't use
1337     //  the service URL itself because the hash code depends on the
1338     //  current service type rather than the original, and we need
1339     //  to be able to distinguish for a non-service: URL if a
1340     //  registration comes in with a different service type from the
1341     //  original. Values are hashtables with key being scope name,
1342     //  values are hashtables with lang tag key. Ultimate values are
1343     //  a vector of List objects for lists in which List.record is
1344     //  inserted. This table is used to perform deletions and for
1345     //  finding the attributes associated with a particular URL.
1346 
1347     private Hashtable urlScopeLangTable = new Hashtable();
1348 
1349     //  The sstLocales Table
1350     //
1351     //  The scope/service type v.s. number of languages. Keys are
1352     //  the type/scope, values are a hashtable keyed by lang tag.
1353     //  Values in the lang tag table are Integer objects giving
1354     //  the number of registrations for that type/scope in the
1355     //  given locale.
1356 
1357     private Hashtable sstLocales = new Hashtable();
1358 
1359     // A queue of records sorted according to expiration time.
1360 
1361     BtreeVector ageOutQueue = new BtreeVector();
1362 
1363     // Constants that indicate whether there are any registrations.
1364 
1365     private final static int NO_REGS = 0;
1366     private final static int NO_REGS_IN_LOCALE = 1;
1367     private final static int REGS_IN_LOCALE = 2;
1368 
1369     // Boot time. For DAAdvert timestamps.
1370 
1371     private long bootTime = SLPConfig.currentSLPTime();
1372 
1373     //
1374     // ServiceStore Interface Methods.
1375     //
1376 
1377     /**
1378      * Return the time since the last stateless reboot
1379      * of the ServiceStore.
1380      *
1381      * @return A Long giving the time since the last stateless reboot,
1382      *         in NTP format.
1383      */
1384 
1385     public long getStateTimestamp() {
1386 
1387 	return bootTime;
1388 
1389     }
1390 
1391     /**
1392      * Age out all records whose time has expired.
1393      *
1394      * @param deleted A Vector for return of ServiceStore.Service records
1395      *		     containing deleted services.
1396      * @return The time interval until another table walk must be done,
1397      *         in milliseconds.
1398      *
1399      */
1400 
1401     synchronized public long ageOut(Vector deleted) {
1402 
1403 	// Get the ageOut queue and remove all records whose
1404 	// time has popped.
1405 
1406 	SLPConfig conf = SLPConfig.getSLPConfig();
1407 	boolean traceDrop = conf.traceDrop();
1408 	Vector queue = ageOutQueue.getContents();
1409 
1410 	// Go through the queue, dropping records that
1411 	//  have expired.
1412 
1413 	int i;
1414 
1415 	for (i = 0; i < queue.size(); i++) {
1416 	    RegRecord qRec = (RegRecord)queue.elementAt(i);
1417 	    long exTime = ((Long)(qRec.value)).longValue();
1418 	    long time = System.currentTimeMillis();
1419 
1420 	    // Break out when none expire now.
1421 
1422 	    if (exTime > time) {
1423 		break;
1424 
1425 	    }
1426 
1427 	    // Remove the element from the queue.
1428 
1429 	    /*
1430 	     *  Must decrement the index 'i' otherwise the next iteration
1431 	     *  around the loop will miss the element immediately after
1432 	     *  the element removed.
1433 	     *
1434 	     *  WARNING: Do not use 'i' again until the loop has
1435 	     *           iterated as it may, after decrementing,
1436 	     *           be negative.
1437 	     */
1438 	    queue.removeElementAt(i);
1439 	    i--;
1440 
1441 	    // Deregister all on this list. We
1442 	    // take specific care to save the next
1443 	    // list element before we deregister, otherwise
1444 	    // it will be gone after the deregister.
1445 
1446 	    List l = qRec.head.next;
1447 
1448 	    while (l != null) {
1449 		ServiceRecordInMemory rec = l.record;
1450 		ServiceURL url = rec.getServiceURL();
1451 		Vector scopes = rec.getScopes();
1452 		Locale locale = rec.getLocale();
1453 
1454 		if (traceDrop) {
1455 		    conf.writeLog("ssim_ageout",
1456 				  new Object[] {
1457 			url,
1458 			    rec.getAttrList(),
1459 			    scopes,
1460 			    locale,
1461 			    rec.getURLSignature(),
1462 			    rec.getAttrSignature(),
1463 			    Long.toString(time),
1464 			    Long.toString(exTime)});
1465 		}
1466 
1467 		// Save the record for the service table, in case more
1468 		// processing needed.
1469 
1470 		deleted.addElement(rec);
1471 
1472 		// Save l.next NOW before deregisterInternal() removes it!
1473 
1474 		l = l.next;
1475 
1476 		String lang = locale.getLanguage();
1477 
1478 		deregisterInternal(url, scopes, lang);
1479 
1480 	    }
1481 	}
1482 
1483 	// Calculate the new sleep time. If there's anything in the vector,
1484 	// then use element 0, because the vector is sorted by time
1485 	// and that will be minimum. Otherwise, use the maximum.
1486 
1487 	long newSleepy = Defaults.lMaxSleepTime;
1488 
1489 	if (queue.size() > 0) {
1490 	    RegRecord rec = (RegRecord)queue.elementAt(0);
1491 
1492 	    newSleepy =
1493 		((Long)(rec.value)).longValue() - System.currentTimeMillis();
1494 
1495 	    newSleepy = (newSleepy > 0 ? newSleepy:0);
1496 						// it will wake right up, but
1497 						// so what?
1498 
1499 	}
1500 
1501 	return newSleepy;
1502 
1503     }
1504 
1505     /**
1506      * Create a new registration with the given parameters.
1507      *
1508      * @param url The ServiceURL.
1509      * @param attrs The Vector of ServiceLocationAttribute objects.
1510      * @param locale The Locale.
1511      * @param scopes Vector of scopes in which this record is registered.
1512      * @param urlSig auth block Hashtable for URL signature, or null if none.
1513      * @param attrSig auth block Hashtable for URL signature, or null if none.
1514      * @return True if there is an already existing registration that
1515      *         this one replaced.
1516      * @exception ServiceLocationException Thrown if any
1517      *			error occurs during registration or if the table
1518      * 			requires a network connection that failed. This
1519      *			includes timeout failures.
1520      */
1521 
1522     synchronized public boolean
1523 	register(ServiceURL url, Vector attrs,
1524 		 Vector scopes, Locale locale,
1525 		 Hashtable urlSig, Hashtable attrSig)
1526 	throws ServiceLocationException {
1527 
1528 	boolean existing = false;
1529 
1530 	String lang = locale.getLanguage();
1531 
1532 	// Find an existing record, in any set of scopes having this language.
1533 
1534 	ServiceRecordInMemory rec = findExistingRecord(url, null, lang);
1535 
1536 	// Deregister from existing scopes, if there is an existing record.
1537 
1538 	if (rec != null) {
1539 	    if (urlSig != null) {
1540 		// Ensure that the rereg SPI set and the record's SPI set are
1541 		// equivalent. We need only check the URL sigs here, since
1542 		// this operation is equivalent to a dereg followed by a reg,
1543 		// and dereg requires only URL auth blocks.
1544 
1545 		Enumeration spis = urlSig.keys();
1546 		while (spis.hasMoreElements()) {
1547 		    Object spi = spis.nextElement();
1548 		    if (rec.urlSig.remove(spi) == null) {
1549 			throw new ServiceLocationException(
1550 				ServiceLocationException.AUTHENTICATION_FAILED,
1551 				"not_all_spis_present",
1552 				new Object[] {spi});
1553 		    }
1554 		}
1555 		if (rec.urlSig.size() != 0) {
1556 		    // not all required SPIs were present in SrvReg
1557 		    throw new ServiceLocationException(
1558 				ServiceLocationException.AUTHENTICATION_FAILED,
1559 				"not_all_spis_present",
1560 				new Object[] {rec.urlSig.keys()});
1561 		}
1562 	    }
1563 
1564 	    deregisterInternal(url, rec.getScopes(), lang);
1565 	    existing = true;
1566 
1567 	}
1568 
1569 	// Create a new record to register.
1570 
1571 	rec = new ServiceRecordInMemory(url, attrs, scopes,
1572 					locale, urlSig, attrSig);
1573 
1574 	// Add new registration.
1575 
1576 	registerInternal(rec);
1577 
1578 	return existing;
1579 
1580     }
1581 
1582     /**
1583      * Deregister a ServiceURL from the database for every locale
1584      * and every scope. There will be only one record for each URL
1585      * and locale deregistered, regardless of the number of scopes in
1586      * which the URL was registered, since the attributes will be the
1587      * same in each scope if the locale is the same.
1588      *
1589      * @param url The ServiceURL
1590      * @param scopes Vector of scopes.
1591      * @param urlSig The URL signature, if any.
1592      * @exception ServiceLocationException Thrown if the
1593      *			ServiceStore does not contain the URL, or if any
1594      *			error occurs during the operation, or if the table
1595      * 			requires a network connection that failed. This
1596      *			includes timeout failures.
1597      */
1598 
1599     synchronized public void
1600 	deregister(ServiceURL url, Vector scopes, Hashtable urlSig)
1601 	throws ServiceLocationException {
1602 
1603 	// Find existing record. Any locale will do.
1604 
1605 	ServiceRecordInMemory oldRec =
1606 	    findExistingRecord(url, scopes, null);
1607 
1608 	// Error if none.
1609 
1610 	if (oldRec == null) {
1611 	    throw
1612 		new ServiceLocationException(
1613 				ServiceLocationException.INVALID_REGISTRATION,
1614 				"ssim_no_rec",
1615 				new Object[] {url});
1616 
1617 	}
1618 
1619 	// verify that the dereg SPI set and the record's SPI set are
1620 	// equivalent
1621 	if (urlSig != null) {
1622 	    Enumeration spis = urlSig.keys();
1623 	    while (spis.hasMoreElements()) {
1624 		Object spi = spis.nextElement();
1625 		if (oldRec.urlSig.remove(spi) == null) {
1626 		    throw new ServiceLocationException(
1627 				ServiceLocationException.AUTHENTICATION_FAILED,
1628 				"not_all_spis_present",
1629 				new Object[] {spi});
1630 		}
1631 	    }
1632 	    if (oldRec.urlSig.size() != 0) {
1633 		// not all required SPIs were present in SrvDereg
1634 		throw new ServiceLocationException(
1635 				ServiceLocationException.AUTHENTICATION_FAILED,
1636 				"not_all_spis_present",
1637 				new Object[] {oldRec.urlSig.keys()});
1638 	    }
1639 	}
1640 
1641 	/*
1642 	 * Deregister the URL for all locales. Use the recorded service URL
1643 	 * because the one passed by the client is possibly incomplete e.g.
1644 	 * lacking the service type.
1645 	 */
1646 
1647 	deregisterInternal(oldRec.getServiceURL(), scopes, null);
1648 
1649     }
1650 
1651     /**
1652      * Update the service registration with the new parameters, adding
1653      * attributes and updating the service URL's lifetime.
1654      *
1655      * @param url The ServiceURL.
1656      * @param attrs The Vector of ServiceLocationAttribute objects.
1657      * @param locale The Locale.
1658      * @param scopes Vector of scopes in which this record is registered.
1659      * @exception ServiceLocationException Thrown if any
1660      *			error occurs during registration or if the table
1661      * 			requires a network connection that failed. This
1662      *			includes timeout failures.
1663      */
1664 
1665     synchronized public void
1666 	updateRegistration(ServiceURL url, Vector attrs,
1667 			   Vector scopes, Locale locale)
1668 	throws ServiceLocationException {
1669 
1670 	String lang = locale.getLanguage();
1671 	ServiceRecordInMemory oldRec =
1672 	    findExistingRecord(url, scopes, lang);
1673 
1674 	// Error if none.
1675 
1676 	if (oldRec == null) {
1677 	    throw
1678 		new ServiceLocationException(
1679 				ServiceLocationException.INVALID_UPDATE,
1680 				"ssim_no_rec",
1681 				new Object[] {url});
1682 
1683 	}
1684 
1685 	// If this is a nonServiceURL, check whether it's registered
1686 	//  under a different service type.
1687 
1688 	ServiceType type = url.getServiceType();
1689 
1690 	if (!type.isServiceURL()) {
1691 	    checkForExistingUnderOtherServiceType(url, scopes);
1692 
1693 	}
1694 
1695 	// Deregister the URL in this locale.
1696 
1697 	deregisterInternal(url, scopes, lang);
1698 
1699 	// Create a new record to update.
1700 
1701 	ServiceRecordInMemory rec =
1702 	    new ServiceRecordInMemory(url, attrs, scopes,
1703 				      locale, null, null);
1704 
1705 	// Merge old record into new.
1706 
1707 	mergeOldRecordIntoNew(oldRec, rec);
1708 
1709 	// Add the new record.
1710 
1711 	registerInternal(rec);
1712     }
1713 
1714     /**
1715      * Delete the attributes from the ServiceURL object's table entries.
1716      * Delete for every locale that has the attributes and every scope.
1717      * Note that the attribute tags must be lower-cased in the locale of
1718      * the registration, not in the locale of the request.
1719      *
1720      * @param url The ServiceURL.
1721      * @param scopes Vector of scopes.
1722      * @param attrTags The Vector of String
1723      *			objects specifying the attribute tags of
1724      *			the attributes to delete.
1725      * @param locale Locale of the request.
1726      * @exception ServiceLocationException Thrown if the
1727      *			ServiceStore does not contain the URL or if any
1728      *			error occurs during the operation or if the table
1729      * 			requires a network connection that failed. This
1730      *			includes timeout failures.
1731      */
1732 
1733     synchronized public void
1734 	deleteAttributes(ServiceURL url,
1735 			 Vector scopes,
1736 			 Vector attrTags,
1737 			 Locale locale)
1738 	throws ServiceLocationException {
1739 
1740 	String lang = SLPConfig.localeToLangTag(locale);
1741 
1742 	// Get the scope level from urlScopeLangTable.
1743 
1744 	Hashtable scopeLevel =
1745 	    (Hashtable)urlScopeLangTable.get(url.toString());
1746 
1747 	// Error if no old record to update.
1748 
1749 	if (scopeLevel == null) {
1750 	    throw
1751 		new ServiceLocationException(
1752 				ServiceLocationException.INVALID_REGISTRATION,
1753 				"ssim_no_rec",
1754 				new Object[] {url});
1755 
1756 	}
1757 
1758 	// Check existing records to be sure that the scopes
1759 	//  match. Attributes must be the same across
1760 	//  scopes.
1761 
1762 	checkScopeStatus(url,
1763 			 scopes,
1764 			 ServiceLocationException.INVALID_REGISTRATION);
1765 
1766 	// Create attribute patterns for the default locale. This
1767 	//  is an optimization. Only Turkish differs in lower
1768 	//  case from the default. If there are any other exceptions,
1769 	//  we need to move this into the loop.
1770 
1771 	Vector attrPatterns =
1772 	    stringVectorToAttributePattern(attrTags, Defaults.locale);
1773 
1774 	// Look through the language table for this language at scope level.
1775 
1776 	Enumeration en = scopeLevel.keys();
1777 
1778 	Assert.slpassert(en.hasMoreElements(),
1779 		      "ssim_empty_scope_table",
1780 		      new Object[] {url});
1781 
1782 	Hashtable ht = new Hashtable();
1783 	boolean foundIt = false;
1784 
1785 	while (en.hasMoreElements()) {
1786 	    String scope = (String)en.nextElement();
1787 	    Hashtable langLevel = (Hashtable)scopeLevel.get(scope);
1788 	    Enumeration een = langLevel.keys();
1789 
1790 	    Assert.slpassert(een.hasMoreElements(),
1791 			  "ssim_empty_lang_table",
1792 			  new Object[] {url});
1793 
1794 	    // Find the list of records for this language.
1795 
1796 	    Vector listVec = (Vector)langLevel.get(lang);
1797 
1798 	    if (listVec == null) {
1799 		continue;
1800 
1801 	    }
1802 
1803 	    foundIt = true;
1804 
1805 	    List elem = (List)listVec.elementAt(0);
1806 	    ServiceRecordInMemory rec = elem.record;
1807 	    Locale loc = rec.getLocale();
1808 
1809 	    // If we've done this one already, go on.
1810 
1811 	    if (ht.get(rec) != null) {
1812 		continue;
1813 
1814 	    }
1815 
1816 	    ht.put(rec, rec);
1817 
1818 	    // Delete old registration.
1819 
1820 	    deregisterInternal(url, rec.getScopes(), lang);
1821 
1822 	    // Delete attributes from this record.
1823 
1824 	    // If the locale is Turkish, then use the Turkish patterns.
1825 
1826 	    if (loc.getLanguage().equals("tr")) {
1827 		Vector turkishTags =
1828 		    stringVectorToAttributePattern(attrTags, loc);
1829 
1830 		deleteAttributes(rec, turkishTags);
1831 
1832 	    } else {
1833 		deleteAttributes(rec, attrPatterns);
1834 
1835 	    }
1836 
1837 	    // Reregister the record.
1838 
1839 	    registerInternal(rec);
1840 	}
1841 
1842 	// If no record found, report error.
1843 
1844 	if (!foundIt) {
1845 	    throw
1846 		new ServiceLocationException(
1847 				ServiceLocationException.INVALID_REGISTRATION,
1848 				"ssim_no_rec_locale",
1849 				new Object[] {url, locale});
1850 
1851 	}
1852 
1853     }
1854 
1855     /**
1856      * Return a Vector of String containing the service types for this
1857      * scope and naming authority. If there are none, an empty vector is
1858      * returned.
1859      *
1860      * @param namingAuthority The namingAuthority, or "*" if for all.
1861      * @param scopes The scope names.
1862      * @return A Vector of String objects that are the type names, or
1863      *		an empty vector if there are none.
1864      * @exception ServiceLocationException Thrown if any
1865      *			error occurs during the operation or if the table
1866      * 			requires a network connection that failed. This
1867      *			includes timeout failures.
1868      */
1869 
1870     synchronized public Vector
1871 	findServiceTypes(String namingAuthority, Vector scopes)
1872 	throws ServiceLocationException {
1873 
1874 	Vector ret = new Vector();
1875 	Enumeration keys = scopeTypeLangTable.keys();
1876 	boolean isWildCard = namingAuthority.equals("*");
1877 	boolean isIANA = (namingAuthority.length() <= 0);
1878 
1879 	// Get all the keys in the table, look for scope.
1880 
1881 	while (keys.hasMoreElements()) {
1882 	    String sstKey = (String)keys.nextElement();
1883 
1884 	    // Check whether this is an abstract type entry.
1885 	    //  If so, then we ignore it, because we only
1886 	    //  want full type names in the return.
1887 
1888 	    if (isAbstractTypeRecord(sstKey)) {
1889 		continue;
1890 
1891 	    }
1892 
1893 	    // If the scope matches then check the naming authority.
1894 
1895 	    String keyScope = keyScope(sstKey);
1896 
1897 	    if (scopes.contains(keyScope)) {
1898 		String keyType = keyServiceType(sstKey);
1899 
1900 		// If not already there, see if we should add this one to the
1901 		//  vector.
1902 
1903 		if (!ret.contains(keyType)) {
1904 		    ServiceType type = new ServiceType(keyType);
1905 
1906 		    // If wildcard, then simply add it to the vector.
1907 
1908 		    if (isWildCard) {
1909 			ret.addElement(type.toString());
1910 
1911 		    } else {
1912 
1913 			// Check naming authority.
1914 
1915 			String na = type.getNamingAuthority();
1916 
1917 			if (type.isNADefault() && isIANA) { // check for IANA..
1918 			    ret.addElement(type.toString());
1919 
1920 			} else if (namingAuthority.equals(na)) { // Not IANA..
1921 			    ret.addElement(type.toString());
1922 
1923 			}
1924 		    }
1925 		}
1926 	    }
1927 	}
1928 
1929 	return ret;
1930     }
1931 
1932     /**
1933      * Return a Hashtable with the key FS_SERVICES matched to the
1934      * hashtable of ServiceURL objects as key and a vector
1935      * of their scopes as value, and the key FS_SIGTABLE
1936      * matched to a hashtable with ServiceURL objects as key
1937      * and the auth block Hashtable for the URL (if any) for value. The
1938      * returned service URLs will match the service type, scope, query,
1939      * and locale. If there are no signatures, the FS_SIGTABLE
1940      * key returns null. If there are no
1941      * registrations in any locale, FS_SERVICES is bound to an
1942      * empty table.
1943      *
1944      * @param serviceType The service type name.
1945      * @param scope The scope name.
1946      * @param query The query, with any escaped characters as yet unprocessed.
1947      * @param locale The locale in which to lowercase query and search.
1948      * @return A Hashtable with the key FS_SERVICES matched to the
1949      *         hashtable of ServiceURL objects as key and a vector
1950      *         of their scopes as value, and the key FS_SIGTABLE
1951      *         matched to a hashtable with ServiceURL objects as key
1952      *         and the auth block Hashtable for the URL (if any) for value.
1953      *         If there are no registrations in any locale, FS_SERVICES
1954      *	      is bound to an empty table.
1955      * @exception ServiceLocationException Thrown if a parse error occurs
1956      *			during query parsing or if any
1957      *			error occurs during the operation or if the table
1958      * 			requires a network connection that failed. This
1959      *			includes timeout failures.
1960      */
1961 
1962     synchronized public Hashtable
1963 	findServices(String serviceType,
1964 		     Vector scopes,
1965 		     String query,
1966 		     Locale locale)
1967 	throws ServiceLocationException {
1968 
1969 	String lang = locale.getLanguage();
1970 	Parser.ParserRecord ret = new Parser.ParserRecord();
1971 	Hashtable services = null;
1972 	Hashtable signatures = null;
1973 	int i, n = scopes.size();
1974 	int len = 0;
1975 
1976 	// Get the services and signatures tables.
1977 
1978 	services = ret.services;
1979 	signatures = ret.signatures;
1980 
1981 	// Remove leading and trailing spaces.
1982 
1983 	query = query.trim();
1984 	len = query.length();
1985 
1986 	// Check whether there are any registrations for this type/scope/
1987 	//  language tag and, if not, whether there are others.
1988 	//  in another language, but not this one.
1989 
1990 	int regStatus = languageSupported(serviceType, scopes, lang);
1991 
1992 	if (regStatus == NO_REGS_IN_LOCALE) {
1993 	    throw
1994 		new ServiceLocationException(
1995 			ServiceLocationException.LANGUAGE_NOT_SUPPORTED,
1996 			"ssim_lang_unsup",
1997 			new Object[] {locale});
1998 
1999 	} else if (regStatus == REGS_IN_LOCALE) {
2000 
2001 	    // Only do query if regs exist.
2002 
2003 	    for (i = 0; i < n; i++) {
2004 		String scope = (String)scopes.elementAt(i);
2005 		String sstKey =
2006 		    makeScopeTypeLangKey(scope, serviceType, lang);
2007 		STLRecord regRecs =
2008 		    (STLRecord)scopeTypeLangTable.get(sstKey);
2009 
2010 		// If no record for this combo of service type and
2011 		//  scope, continue.
2012 
2013 		if (regRecs == null) {
2014 		    continue;
2015 		}
2016 
2017 		// Special case if the query string is empty. This
2018 		//  indicates that all registrations should be returned.
2019 
2020 		if (len <= 0) {
2021 		    BtreeVector bvec = regRecs.attrSort;
2022 		    ParserBVCollector collector =
2023 			new ParserBVCollector(scopes);
2024 		    collector.prReturns = ret;
2025 
2026 		    // Use the BtreeVector.getAll() method to get all
2027 		    //  registrations. We will end up revisiting some
2028 		    //  list elements because there will be ones
2029 		    //  for multiple attributes, but that will be
2030 		    //  filtered in the BVCollector.setReturn() method.
2031 
2032 		    bvec.getAll(collector);
2033 
2034 		} else {
2035 
2036 		    // Otherwise, use the LDAPv3 parser to evaluate.
2037 
2038 		    InMemoryEvaluator ev =
2039 			new InMemoryEvaluator(regRecs.attrValueSort,
2040 					      regRecs.attrSort,
2041 					      scopes);
2042 
2043 		    Parser.parseAndEvaluateQuery(query, ev, locale, ret);
2044 
2045 		}
2046 	    }
2047 	}
2048 
2049 	// Create return hashtable.
2050 
2051 	Hashtable ht = new Hashtable();
2052 
2053 	// Set up return hashtable.
2054 
2055 	ht.put(ServiceStore.FS_SERVICES, services);
2056 
2057 	// Put in signatures if there.
2058 
2059 	if (signatures.size() > 0) {
2060 	    ht.put(ServiceStore.FS_SIGTABLE, signatures);
2061 
2062 	}
2063 
2064 	return ht;
2065     }
2066 
2067     /**
2068      * Return a Hashtable with key FA_ATTRIBUTES matched to the
2069      * vector of ServiceLocationAttribute objects and key FA_SIG
2070      * matched to the auth block Hashtable for the attributes (if any)
2071      * The attribute objects will have tags matching the tags in
2072      * the input parameter vector. If there are no registrations in any locale,
2073      * FA_ATTRIBUTES is an empty vector.
2074      *
2075      * @param url The ServiceURL for which the records should be returned.
2076      * @param scopes The scope names for which to search.
2077      * @param attrTags The Vector of String
2078      *			objects containing the attribute tags.
2079      * @param locale The locale in which to lower case tags and search.
2080      * @return A Hashtable with a vector of ServiceLocationAttribute objects
2081      *         as the key and the auth block Hashtable for the attributes
2082      *         (if any) as the value.
2083      *         If there are no registrations in any locale, FA_ATTRIBUTES
2084      *         is an empty vector.
2085      * @exception ServiceLocationException Thrown if any
2086      *			error occurs during the operation or if the table
2087      * 			requires a network connection that failed. This
2088      *			includes timeout failures. An error should be
2089      *			thrown if the tag vector is for a partial request
2090      *			and any of the scopes are protected.
2091      */
2092 
2093     synchronized public Hashtable
2094 	findAttributes(ServiceURL url,
2095 		       Vector scopes,
2096 		       Vector attrTags,
2097 		       Locale locale)
2098 	throws ServiceLocationException {
2099 
2100 	Hashtable ht = new Hashtable();
2101 	Vector ret = new Vector();
2102 	String lang = locale.getLanguage();
2103 	Hashtable sig = null;
2104 
2105 	// Check whether there are any registrations for this scope/type
2106 	//  language and, if not, whether there are others.
2107 	//  in another language, but not this one.
2108 
2109 	int regStatus =
2110 	    languageSupported(url.getServiceType().toString(), scopes, lang);
2111 
2112 	if (regStatus == NO_REGS_IN_LOCALE) {
2113 	    throw
2114 		new ServiceLocationException(
2115 			ServiceLocationException.LANGUAGE_NOT_SUPPORTED,
2116 			"ssim_lang_unsup",
2117 			new Object[] {locale});
2118 
2119 	} else if (regStatus == REGS_IN_LOCALE) {
2120 
2121 	    // Only if there are any regs at all.
2122 
2123 	    // Process string tags into pattern objects. Note that, here,
2124 	    //  the patterns are locale specific because the locale of
2125 	    //  the request determines how the attribute tags are lower
2126 	    //  cased.
2127 
2128 	    attrTags = stringVectorToAttributePattern(attrTags, locale);
2129 
2130 	    // Return attributes from the matching URL record.
2131 
2132 	    Hashtable scopeLevel =
2133 		(Hashtable)urlScopeLangTable.get(url.toString());
2134 
2135 	    // If nothing there, then simply return. The URL isn't
2136 	    //  registered.
2137 
2138 	    if (scopeLevel != null) {
2139 
2140 		// We reuse ht here for attributes.
2141 
2142 		int i, n = scopes.size();
2143 
2144 		for (i = 0; i < n; i++) {
2145 		    String scope = (String)scopes.elementAt(i);
2146 		    Hashtable langLevel =
2147 			(Hashtable)scopeLevel.get(scope);
2148 
2149 		    // If no registration in this scope, continue.
2150 
2151 		    if (langLevel == null) {
2152 			continue;
2153 		    }
2154 
2155 		    // Get the vector of lists.
2156 
2157 		    Vector listVec = (Vector)langLevel.get(lang);
2158 
2159 		    // If no registration in this locale, continue.
2160 
2161 		    if (listVec == null) {
2162 			continue;
2163 
2164 		    }
2165 
2166 		    // Get the service record.
2167 
2168 		    List elem = (List)listVec.elementAt(0);
2169 		    ServiceRecordInMemory rec = elem.record;
2170 
2171 		    // Once we've found *the* URL record, we can leave the loop
2172 		    //  because there is only one record per locale.
2173 
2174 		    findMatchingAttributes(rec, attrTags, ht, ret);
2175 
2176 		    // Clear out the hashtable. We reuse it for the return.
2177 
2178 		    ht.clear();
2179 
2180 		    // Store the return vector and the signatures, if any.
2181 
2182 		    ht.put(ServiceStore.FA_ATTRIBUTES, ret);
2183 
2184 		    sig = rec.getAttrSignature();
2185 
2186 		    if (sig != null) {
2187 			ht.put(ServiceStore.FA_SIG, sig);
2188 
2189 		    }
2190 
2191 		    break;
2192 
2193 		}
2194 	    }
2195 	}
2196 
2197 	// Put in the empty vector, in case there are no regs at all.
2198 
2199 	if (ht.size() <= 0) {
2200 	    ht.put(ServiceStore.FA_ATTRIBUTES, ret);
2201 
2202 	}
2203 
2204 	return ht;
2205     }
2206 
2207     /**
2208      * Return a Vector of ServiceLocationAttribute objects with attribute tags
2209      * matching the tags in the input parameter vector for all service URL's
2210      * of the service type. If there are no registrations
2211      * in any locale, an empty vector is returned.
2212      *
2213      * @param serviceType The service type name.
2214      * @param scopes The scope names for which to search.
2215      * @param attrTags The Vector of String
2216      *			objects containing the attribute tags.
2217      * @param locale The locale in which to lower case tags.
2218      * @return A Vector of ServiceLocationAttribute objects matching the query.
2219      *         If no match occurs but there are registrations
2220      * 	      in other locales, null is returned. If there are no registrations
2221      *         in any locale, an empty vector is returned.
2222      * @exception ServiceLocationException Thrown if any
2223      *		 error occurs during the operation or if the table
2224      * 		 requires a network connection that failed. This
2225      *		 includes timeout failures. An error should also be
2226      *            signalled if any of the scopes are protected.
2227      */
2228 
2229     synchronized public Vector
2230 	findAttributes(String serviceType,
2231 		       Vector scopes,
2232 		       Vector attrTags,
2233 		       Locale locale)
2234 	throws ServiceLocationException {
2235 
2236 	String lang = locale.getLanguage();
2237 	Vector ret = new Vector();
2238 
2239 	// Check whether there are any registrations for this type/scope/
2240 	//  language and, if not, whether there are others.
2241 	//  in another language, but not this one.
2242 
2243 	int regStatus = languageSupported(serviceType, scopes, lang);
2244 
2245 	if (regStatus == NO_REGS_IN_LOCALE) {
2246 	    throw
2247 		new ServiceLocationException(
2248 			ServiceLocationException.LANGUAGE_NOT_SUPPORTED,
2249 			"ssim_lang_unsup",
2250 			new Object[] {locale});
2251 
2252 	} else if (regStatus == REGS_IN_LOCALE) {
2253 
2254 	    // Process string tags into pattern objects. Note that, here,
2255 	    //  the patterns are locale specific because the locale of
2256 	    //  the request determines how the attribute tags are lower
2257 	    //  cased.
2258 
2259 	    attrTags = stringVectorToAttributePattern(attrTags, locale);
2260 	    int len = attrTags.size();
2261 
2262 	    // Make a collector for accessing the BtreeVector.
2263 
2264 	    BVCollector collector =
2265 		new AttributeBVCollector(attrTags, ret);
2266 	    int i, n = scopes.size();
2267 
2268 	    for (i = 0; i < n; i++) {
2269 		String scope = (String)scopes.elementAt(i);
2270 		String sstKey =
2271 		    makeScopeTypeLangKey(scope, serviceType, lang);
2272 		STLRecord regRecs = (STLRecord)scopeTypeLangTable.get(sstKey);
2273 
2274 		// If no service type and scope, go to next scope.
2275 
2276 		if (regRecs == null) {
2277 		    continue;
2278 		}
2279 
2280 		// Get BtreeVector with all attributes for searching.
2281 
2282 		BtreeVector bvec = regRecs.attrSort;
2283 
2284 		// If there are no tags, then simply return everything in
2285 		//  the BtreeVector.
2286 
2287 		if (len <= 0) {
2288 		    bvec.getAll(collector);
2289 
2290 		} else {
2291 
2292 		    // Use Btree vector to match the attribute tag patterns,
2293 		    //  returning matching records.
2294 
2295 		    int j;
2296 
2297 		    for (j = 0; j < len; j++) {
2298 			AttributePattern pat =
2299 			    (AttributePattern)attrTags.elementAt(j);
2300 
2301 			bvec.matchEqual(pat, collector);
2302 
2303 		    }
2304 		}
2305 	    }
2306 	}
2307 
2308 	return ret;
2309     }
2310 
2311     /**
2312      * Obtain the record matching the service URL and locale.
2313      *
2314      * @param URL The service record to match.
2315      * @param locale The locale of the record.
2316      * @return The ServiceRecord object, or null if none.
2317      */
2318 
2319     synchronized public ServiceStore.ServiceRecord
2320 	getServiceRecord(ServiceURL URL, Locale locale) {
2321 
2322 	if (URL == null || locale == null) {
2323 	    return null;
2324 
2325 	}
2326 
2327 	// Search in all scopes.
2328 
2329 	return findExistingRecord(URL,
2330 				  null,
2331 				  SLPConfig.localeToLangTag(locale));
2332 
2333     }
2334 
2335     /**
2336      * Obtains service records with scopes matching from vector scopes.
2337      * If scopes is null, then returns all records.
2338      *
2339      * @param scopes Vector of scopes to match.
2340      * @return Enumeration   Of ServiceRecord Objects.
2341      */
2342     synchronized public Enumeration getServiceRecordsByScope(Vector scopes) {
2343 
2344 	// Use a scope collector.
2345 
2346 	Vector records = new Vector();
2347 	BVCollector collector =
2348 	    new ScopeBVCollector(records, scopes);
2349 
2350 	Enumeration keys = scopeTypeLangTable.keys();
2351 
2352 	while (keys.hasMoreElements()) {
2353 	    String sstKey = (String)keys.nextElement();
2354 	    STLRecord regRecs = (STLRecord)scopeTypeLangTable.get(sstKey);
2355 
2356 	    // Get all records.
2357 
2358 	    BtreeVector bvec = regRecs.attrSort;
2359 	    bvec.getAll(collector);
2360 
2361 	}
2362 
2363 	return records.elements();
2364     }
2365 
2366 
2367     /**
2368      * Dump the service store to the log.
2369      *
2370      */
2371 
2372     synchronized public void dumpServiceStore() {
2373 
2374 	SLPConfig conf = SLPConfig.getSLPConfig();
2375 
2376 	conf.writeLogLine("ssim_dump_start",
2377 			  new Object[] {this});
2378 
2379 	Enumeration keys = scopeTypeLangTable.keys();
2380 
2381 	while (keys.hasMoreElements()) {
2382 	    String sstKey = (String)keys.nextElement();
2383 	    STLRecord regRec = (STLRecord)scopeTypeLangTable.get(sstKey);
2384 
2385 	    // If the service type is abstract, then skip it. It will be
2386 	    //  displayed when the concrete type is.
2387 
2388 	    if (regRec.isAbstract) {
2389 		continue;
2390 
2391 	    }
2392 
2393 	    // Get all records.
2394 
2395 	    BtreeVector bvec = regRec.attrSort;
2396 	    Vector vReturns = new Vector();
2397 	    BVCollector collector = new AllBVCollector(vReturns);
2398 
2399 	    bvec.getAll(collector);
2400 
2401 	    // Now write them out.
2402 
2403 	    int i, n = vReturns.size();
2404 
2405 	    for (i = 0; i < n; i++) {
2406 		ServiceRecordInMemory rec =
2407 		    (ServiceRecordInMemory)vReturns.elementAt(i);
2408 
2409 		writeRecordToLog(conf, rec);
2410 	    }
2411 	}
2412 
2413 	conf.writeLog("ssim_dump_end",
2414 		      new Object[] {this});
2415     }
2416 
2417     //
2418     // Protected/private methods.
2419     //
2420 
2421     // Register the record without any preliminaries. We assume that
2422     //  any old records have been removed and merged into this one,
2423     //  as necessary.
2424 
2425     private void registerInternal(ServiceRecordInMemory rec) {
2426 
2427 	ServiceURL surl = rec.getServiceURL();
2428 	ServiceType type = surl.getServiceType();
2429 	String serviceType = type.toString();
2430 	String abstractTypeName = type.getAbstractTypeName();
2431 	Locale locale = rec.getLocale();
2432 	String lang = locale.getLanguage();
2433 	Vector scopes = rec.getScopes();
2434 
2435 	// Make one age out queue entry. It will go into
2436 	//  all scopes, but that's OK.
2437 
2438 	List ageOutElem = addToAgeOutQueue(rec);
2439 
2440 	// Go through all scopes.
2441 
2442 	int i, n = scopes.size();
2443 
2444 	for (i = 0; i < n; i++) {
2445 	    String scope = (String)scopes.elementAt(i);
2446 
2447 	    // Initialize the urltable list vector for this URL.
2448 
2449 	    Vector listVec =
2450 		initializeURLScopeLangTableVector(surl, scope, lang);
2451 
2452 	    // Add to scope/type/lang table.
2453 
2454 	    addRecordToScopeTypeLangTable(scope,
2455 					  serviceType,
2456 					  lang,
2457 					  false,
2458 					  rec,
2459 					  listVec);
2460 
2461 	    // Add a new service type/scope record for this locale.
2462 
2463 	    addTypeLocale(serviceType, scope, lang);
2464 
2465 	    // Add ageOut record, so that it gets deleted when
2466 	    //  the record does.
2467 
2468 	    listVec.addElement(ageOutElem);
2469 
2470 	    // If the type is an abstract type, then add
2471 	    //  separate records.
2472 
2473 	    if (type.isAbstractType()) {
2474 		addRecordToScopeTypeLangTable(scope,
2475 					      abstractTypeName,
2476 					      lang,
2477 					      true,
2478 					      rec,
2479 					      listVec);
2480 		addTypeLocale(abstractTypeName, scope, lang);
2481 
2482 	    }
2483 	}
2484     }
2485 
2486     // Create a urlScopeLangTable record for this URL.
2487 
2488     private Vector
2489 	initializeURLScopeLangTableVector(ServiceURL url,
2490 					  String scope,
2491 					  String lang) {
2492 
2493 	// Get scope level, creating if new.
2494 
2495 	Hashtable scopeLevel =
2496 	    (Hashtable)urlScopeLangTable.get(url.toString());
2497 
2498 	if (scopeLevel == null) {
2499 	    scopeLevel = new Hashtable();
2500 	    urlScopeLangTable.put(url.toString(), scopeLevel);
2501 
2502 	}
2503 
2504 	// Get lang level, creating if new.
2505 
2506 	Hashtable langLevel =
2507 	    (Hashtable)scopeLevel.get(scope);
2508 
2509 	if (langLevel == null) {
2510 	    langLevel = new Hashtable();
2511 	    scopeLevel.put(scope, langLevel);
2512 
2513 	}
2514 
2515 	// Check whether there's anything already there.
2516 	//  Bug if so.
2517 
2518 	Assert.slpassert(langLevel.get(lang) == null,
2519 		      "ssim_url_lang_botch",
2520 		      new Object[] {lang,
2521 					url,
2522 					scope});
2523 
2524 	// Add a new list vector, and return it.
2525 
2526 	Vector listVec = new Vector();
2527 
2528 	langLevel.put(lang, listVec);
2529 
2530 	return listVec;
2531 
2532     }
2533 
2534     // Add a record to the scope/type/language table.
2535 
2536     private void
2537 	addRecordToScopeTypeLangTable(String scope,
2538 				      String serviceType,
2539 				      String lang,
2540 				      boolean isAbstract,
2541 				      ServiceRecordInMemory rec,
2542 				      Vector listVec) {
2543 
2544 	// Make key for scope/type/language table.
2545 
2546 	String stlKey = makeScopeTypeLangKey(scope, serviceType, lang);
2547 
2548 	// Get record for scope/type/lang.
2549 
2550 	STLRecord trec = (STLRecord)scopeTypeLangTable.get(stlKey);
2551 
2552 	// If it's not there, make it.
2553 
2554 	if (trec == null) {
2555 	    trec = new STLRecord(isAbstract);
2556 	    scopeTypeLangTable.put(stlKey, trec);
2557 
2558 	}
2559 
2560 	// Otherwise, add record to all.
2561 
2562 	addRecordToAttrValueSort(trec.attrValueSort, rec, listVec);
2563 	addRecordToAttrSort(trec.attrSort, rec, listVec);
2564 
2565     }
2566 
2567     // Add a new record into the attr value table.
2568 
2569     private void
2570 	addRecordToAttrValueSort(Hashtable table,
2571 				 ServiceRecordInMemory rec,
2572 				 Vector listVec) {
2573 
2574 	Vector attrList = rec.getAttrList();
2575 	int i, n = attrList.size();
2576 
2577 	// Go through the attribute list.
2578 
2579 	for (i = 0; i < n; i++) {
2580 	    ServerAttribute attr =
2581 		(ServerAttribute)attrList.elementAt(i);
2582 	    AttributeString tag = attr.idPattern;
2583 	    Vector values = attr.values;
2584 
2585 	    // If a type table record exists, use it. Otherwise,
2586 	    //  create a newly initialized one.
2587 
2588 	    Hashtable ttable = (Hashtable)table.get(tag);
2589 
2590 	    if (ttable == null) {
2591 		ttable = makeAttrTypeTable();
2592 		table.put(tag, ttable);
2593 
2594 	    }
2595 
2596 	    // Get the class of values.
2597 
2598 	    String typeKey = null;
2599 
2600 	    if (values == null) {
2601 
2602 		// We're done, since there are no attributes to add.
2603 
2604 		continue;
2605 
2606 	    } else {
2607 		Object val = values.elementAt(0);
2608 
2609 		typeKey = val.getClass().getName();
2610 	    }
2611 
2612 	    // Get the BtreeVector.
2613 
2614 	    BtreeVector bvec =
2615 		(BtreeVector)ttable.get(typeKey);
2616 
2617 	    // Insert a record for each value.
2618 
2619 	    int j, m = values.size();
2620 
2621 	    for (j = 0; j < m; j++) {
2622 		List elem = bvec.add(values.elementAt(j), rec);
2623 
2624 		// Put the element into the deletion table.
2625 
2626 		listVec.addElement(elem);
2627 	    }
2628 	}
2629     }
2630 
2631     // Return a newly initialized attribute type table. It will
2632     //  have a hash for each allowed type, with a new BtreeVector
2633     //  attached.
2634 
2635     private Hashtable makeAttrTypeTable() {
2636 
2637 	Hashtable ret = new Hashtable();
2638 
2639 	ret.put(INTEGER_TYPE, new BtreeVector());
2640 	ret.put(ATTRIBUTE_STRING_TYPE, new BtreeVector());
2641 	ret.put(BOOLEAN_TYPE, new BtreeVector());
2642 	ret.put(OPAQUE_TYPE, new BtreeVector());
2643 
2644 	return ret;
2645     }
2646 
2647     // Add a new record into the attrs table.
2648 
2649     private void
2650 	addRecordToAttrSort(BtreeVector table,
2651 			    ServiceRecordInMemory rec,
2652 			    Vector listVec) {
2653 
2654 	Vector attrList = rec.getAttrList();
2655 	int i, n = attrList.size();
2656 
2657 	// If no attributes, then add with empty string as
2658 	//  the attribute tag.
2659 
2660 	if (n <= 0) {
2661 	    List elem =
2662 		table.add(new AttributeString("", rec.getLocale()), rec);
2663 
2664 	    listVec.addElement(elem);
2665 
2666 	    return;
2667 	}
2668 
2669 	// Iterate through the attribute list, adding to the
2670 	//  BtreeVector with attribute as the sort key.
2671 
2672 	for (i = 0; i < n; i++) {
2673 	    ServerAttribute attr =
2674 		(ServerAttribute)attrList.elementAt(i);
2675 
2676 	    List elem = table.add(attr.idPattern, rec);
2677 
2678 	    // Save for deletion.
2679 
2680 	    listVec.addElement(elem);
2681 
2682 	}
2683     }
2684 
2685     // Add a record to the ageOut queue.
2686 
2687     private List addToAgeOutQueue(ServiceRecordInMemory rec) {
2688 
2689 	Long exTime = new Long(rec.getExpirationTime());
2690 	return ageOutQueue.add(exTime, rec);
2691 
2692     }
2693 
2694     // Remove the URL record from the database.
2695 
2696     private void
2697 	deregisterInternal(ServiceURL url, Vector scopes, String lang) {
2698 
2699 	ServiceType type = url.getServiceType();
2700 
2701 	// To deregister, we only need to find the Vector of List objects
2702 	//  containing the places where this registration is hooked into
2703 	//  lists and unhook them. Garbage collection of other structures
2704 	//  is handled during insertion or in deregisterTypeLocale(),
2705 	//  if there are no more registrations at all.
2706 
2707 	// Find the scope table..
2708 
2709 	Hashtable scopeLangTable =
2710 	    (Hashtable)urlScopeLangTable.get(url.toString());
2711 
2712 	// If it's not there, then maybe not registered.
2713 
2714 	if (scopeLangTable == null) {
2715 	    return;
2716 
2717 	}
2718 
2719 	// For each scope, find the lang table.
2720 
2721 	int i, n = scopes.size();
2722 
2723 	for (i = 0; i < n; i++) {
2724 	    String scope = (String)scopes.elementAt(i);
2725 
2726 	    Hashtable langTable = (Hashtable)scopeLangTable.get(scope);
2727 
2728 	    if (langTable == null) {
2729 		continue;
2730 	    }
2731 
2732 	    // If the locale is non-null, then just deregister from this
2733 	    //  locale.
2734 
2735 	    if (lang != null) {
2736 		deregisterFromLocale(langTable, lang);
2737 
2738 		// Record the deletion in the scope/type table, and
2739 		//  also the number of regs table.
2740 
2741 		deleteTypeLocale(type.toString(), scope, lang);
2742 
2743 		// Check for abstract type as well.
2744 
2745 		if (type.isAbstractType()) {
2746 		    deleteTypeLocale(type.getAbstractTypeName(), scope, lang);
2747 
2748 		}
2749 
2750 	    } else {
2751 
2752 		// Otherwise, deregister all languages.
2753 
2754 		Enumeration en = langTable.keys();
2755 
2756 		while (en.hasMoreElements()) {
2757 		    lang = (String)en.nextElement();
2758 
2759 		    deregisterFromLocale(langTable, lang);
2760 
2761 		    // Record the deletion in the scope/type table, and
2762 		    //  also the number of regs table.
2763 
2764 		    deleteTypeLocale(type.toString(), scope, lang);
2765 
2766 		    // Check for abstract type as well.
2767 
2768 		    if (type.isAbstractType()) {
2769 			deleteTypeLocale(type.getAbstractTypeName(),
2770 					 scope,
2771 					 lang);
2772 
2773 		    }
2774 		}
2775 	    }
2776 
2777 	    // If the table is empty, then remove the lang table.
2778 
2779 	    if (langTable.size() <= 0) {
2780 		scopeLangTable.remove(scope);
2781 
2782 	    }
2783 	}
2784 
2785 	// If all languages were deleted, delete the
2786 	//  urlScopeLangTable record. Other GC handled in
2787 	//  deleteTypeLocale().
2788 
2789 	if (scopeLangTable.size() <= 0) {
2790 	    urlScopeLangTable.remove(url.toString());
2791 
2792 	}
2793 
2794     }
2795 
2796     // Deregister a single locale from the language table.
2797 
2798     private void deregisterFromLocale(Hashtable langTable, String lang) {
2799 
2800 	// Get the Vector containing the list of registrations.
2801 
2802 	Vector regList = (Vector)langTable.get(lang);
2803 
2804 	Assert.slpassert(regList != null,
2805 		      "ssim_null_reg_vector",
2806 		      new Object[] {lang});
2807 
2808 	// Walk down the list of registrations and unhook them from
2809 	//  their respective lists.
2810 
2811 	int i, n = regList.size();
2812 
2813 	for (i = 0; i < n; i++) {
2814 	    List elem = (List)regList.elementAt(i);
2815 
2816 	    elem.delete();
2817 
2818 	}
2819 
2820 	// Remove the locale record.
2821 
2822 	langTable.remove(lang);
2823     }
2824 
2825     // Find an existing record matching the URL by searching in all scopes.
2826     //  The record will be the same for all scopes in the same language.
2827     //  If locale is null, return any. If there are none, return null.
2828 
2829     private ServiceRecordInMemory
2830 	findExistingRecord(ServiceURL surl, Vector scopes, String lang) {
2831 
2832 	ServiceRecordInMemory rec = null;
2833 
2834 	// Look in urlScopeLangTable.
2835 
2836 	Hashtable scopeLevel =
2837 	    (Hashtable)urlScopeLangTable.get(surl.toString());
2838 
2839 	if (scopeLevel != null) {
2840 
2841 	    // If scopes is null, then perform the search for all
2842 	    //  scopes in the table. Otherwise perform it for
2843 	    //  all scopes incoming.
2844 
2845 	    Enumeration en = null;
2846 
2847 	    if (scopes == null) {
2848 		en = scopeLevel.keys();
2849 
2850 	    } else {
2851 		en = scopes.elements();
2852 
2853 	    }
2854 
2855 	    while (en.hasMoreElements()) {
2856 		String scope = (String)en.nextElement();
2857 		Hashtable langLevel = (Hashtable)scopeLevel.get(scope);
2858 
2859 		// If no langLevel table, continue searching.
2860 
2861 		if (langLevel == null) {
2862 		    continue;
2863 
2864 		}
2865 
2866 		Vector listVec = null;
2867 
2868 		// Use lang tag if we have it, otherwise, pick arbitrary.
2869 
2870 		if (lang != null) {
2871 		    listVec = (Vector)langLevel.get(lang);
2872 
2873 		} else {
2874 		    Enumeration llen = langLevel.elements();
2875 
2876 		    listVec = (Vector)llen.nextElement();
2877 
2878 		}
2879 
2880 		// If none for this locale, try the next scope.
2881 
2882 		if (listVec == null) {
2883 		    continue;
2884 
2885 		}
2886 
2887 		// Select out the record.
2888 
2889 		List elem = (List)listVec.elementAt(0);
2890 
2891 		rec = elem.record;
2892 		break;
2893 
2894 	    }
2895 	}
2896 
2897 	return rec;
2898     }
2899 
2900     // Find attributes matching the record and place the matching attributes
2901     //  into the vector. Use the hashtable for collation.
2902 
2903     static void
2904 	findMatchingAttributes(ServiceRecordInMemory rec,
2905 			       Vector attrTags,
2906 			       Hashtable ht,
2907 			       Vector ret)
2908 	throws ServiceLocationException {
2909 
2910 	int len = attrTags.size();
2911 	Vector attrList = rec.getAttrList();
2912 
2913 	// For each attribute, go through the tag vector If an attribute
2914 	//  matches, merge it into the return vector.
2915 
2916 	int i, n = attrList.size();
2917 
2918 	for (i = 0; i < n; i++) {
2919 	    ServerAttribute attr =
2920 		(ServerAttribute)attrList.elementAt(i);
2921 
2922 	    // All attributes match if the pattern vector is
2923 	    //  empty.
2924 
2925 	    if (len <= 0) {
2926 		saveValueIfMatch(attr, null, ht, ret);
2927 
2928 	    } else {
2929 
2930 		// Check each pattern against the attribute id.
2931 
2932 		int j;
2933 
2934 		for (j = 0; j < len; j++) {
2935 		    AttributePattern attrTag =
2936 			(AttributePattern)attrTags.elementAt(j);
2937 
2938 		    saveValueIfMatch(attr, attrTag, ht, ret);
2939 
2940 		}
2941 	    }
2942 	}
2943     }
2944 
2945     // Check the attribute against the pattern. If the pattern is null,
2946     //  then match occurs. Merge the attribute into the vector
2947     //  if match.
2948 
2949     static private void saveValueIfMatch(ServerAttribute attr,
2950 					 AttributePattern attrTag,
2951 					 Hashtable ht,
2952 					 Vector ret)
2953 	throws ServiceLocationException {
2954 
2955 	AttributeString id = attr.idPattern;
2956 
2957 	// We save the attribute value if either
2958 	//  the pattern is null or it matches the attribute id.
2959 
2960 	if (attrTag == null || attrTag.match(id)) {
2961 
2962 	    Vector values = attr.getValues();
2963 
2964 	    // Create new values vector so record copy isn't
2965 	    //  modified.
2966 
2967 	    if (values != null) {
2968 		values = (Vector)values.clone();
2969 
2970 	    }
2971 
2972 	    // Create new attribute so record copy isn't
2973 	    //  modified.
2974 
2975 	    ServiceLocationAttribute nattr =
2976 		new ServiceLocationAttribute(attr.getId(), values);
2977 
2978 	    // Merge duplicate attributes into vector.
2979 
2980 	    ServiceLocationAttribute.mergeDuplicateAttributes(nattr,
2981 							      ht,
2982 							      ret,
2983 							      true);
2984 	}
2985     }
2986 
2987     // Check whether the incoming scopes are the same as existing
2988     //  scopes.
2989 
2990     private void
2991 	checkScopeStatus(ServiceURL surl,
2992 			 Vector scopes,
2993 			 short errCode)
2994 	throws ServiceLocationException {
2995 
2996 	// Drill down in the urlScopeLangTable table.
2997 
2998 	Hashtable scopeLevel =
2999 	    (Hashtable)urlScopeLangTable.get(surl.toString());
3000 
3001 	if (scopeLevel == null) {
3002 	    return;  // not yet registered...
3003 
3004 	}
3005 
3006 	// We need to have exactly the same scopes as in
3007 	//  the registration.
3008 
3009 	int i, n = scopes.size();
3010 	boolean ok = true;
3011 
3012 	if (n != scopeLevel.size()) {
3013 	    ok = false;
3014 
3015 	} else {
3016 
3017 	    for (i = 0; i < n; i++) {
3018 		if (scopeLevel.get(scopes.elementAt(i)) == null) {
3019 		    ok = false;
3020 		    break;
3021 
3022 		}
3023 	    }
3024 	}
3025 
3026 	if (!ok) {
3027 	    throw
3028 		new ServiceLocationException(errCode,
3029 					     "ssim_scope_mis",
3030 					     new Object[0]);
3031 
3032 	}
3033     }
3034 
3035     // Check whether an existing nonservice URL is registered under
3036     //  a different service type.
3037 
3038     private void checkForExistingUnderOtherServiceType(ServiceURL url,
3039 						       Vector scopes)
3040 	throws ServiceLocationException {
3041 
3042 	// Drill down in the urlScopeLangTable table.
3043 
3044 	Hashtable scopeLevel =
3045 	    (Hashtable)urlScopeLangTable.get(url.toString());
3046 
3047 	if (scopeLevel == null) {
3048 	    return; // not yet registered.
3049 
3050 	}
3051 
3052 	// Get hashtable of locale records under scopes. Any scope
3053 	//  will do.
3054 
3055 	Object scope = scopes.elementAt(0);
3056 
3057 	Hashtable localeLevel = (Hashtable)scopeLevel.get(scope);
3058 
3059 	Assert.slpassert(localeLevel != null,
3060 		      "ssim_null_lang_table",
3061 		      new Object[] {scope});
3062 
3063 	// Get a record from any locale.
3064 
3065 	Enumeration en = localeLevel.elements();
3066 
3067 	Assert.slpassert(en.hasMoreElements(),
3068 		      "ssim_empty_lang_table",
3069 		      new Object[] {scope});
3070 
3071 	// Get vector of registrations.
3072 
3073 	Vector vec = (Vector)en.nextElement();
3074 
3075 	Assert.slpassert(vec.size() > 0,
3076 		      "ssim_empty_reg_vector",
3077 		      new Object[] {scope});
3078 
3079 	List elem = (List)vec.elementAt(0);
3080 
3081 	// OK, now check the registration.
3082 
3083 	ServiceURL recURL = elem.record.getServiceURL();
3084 	ServiceType recType = recURL.getServiceType();
3085 
3086 	if (!recType.equals(url.getServiceType())) {
3087 	    throw
3088 		new ServiceLocationException(
3089 				ServiceLocationException.INVALID_UPDATE,
3090 				"ssim_st_already",
3091 				new Object[0]);
3092 	}
3093     }
3094 
3095     // Merge old record into new record.
3096 
3097     final private void mergeOldRecordIntoNew(ServiceRecordInMemory oldRec,
3098 					     ServiceRecordInMemory newRec)
3099 	throws ServiceLocationException {
3100 
3101 	Vector newAttrs = newRec.getAttrList();
3102 	Vector oldAttrs = oldRec.getAttrList();
3103 	Hashtable ht = new Hashtable();
3104 
3105 	// Charge up the hashtable with the new attributes.
3106 
3107 	int i, n = newAttrs.size();
3108 
3109 	for (i = 0; i < n; i++) {
3110 	    ServerAttribute attr =
3111 		(ServerAttribute)newAttrs.elementAt(i);
3112 
3113 	    ht.put(attr.getId().toLowerCase(), attr);
3114 
3115 	}
3116 
3117 	// Merge in the old attributes.
3118 
3119 	n = oldAttrs.size();
3120 
3121 	for (i = 0; i < n; i++) {
3122 	    ServerAttribute attr =
3123 		(ServerAttribute)oldAttrs.elementAt(i);
3124 
3125 	    if (ht.get(attr.getId().toLowerCase()) == null) {
3126 		newAttrs.addElement(attr);
3127 
3128 	    }
3129 	}
3130 
3131 	// Change the attribute vector on the rec.
3132 
3133 	newRec.setAttrList(newAttrs);
3134 
3135 	// Merge old scopes into new.
3136 
3137 	Vector oldScopes = oldRec.getScopes();
3138 	Vector newScopes = newRec.getScopes();
3139 	int j, m = oldScopes.size();
3140 
3141 	for (j = 0; j < m; j++) {
3142 	    String scope = (String)oldScopes.elementAt(j);
3143 
3144 	    if (!newScopes.contains(scope)) {
3145 		newScopes.addElement(scope);
3146 
3147 	    }
3148 	}
3149 
3150 	// Note that we don't have to merge security because there
3151 	//  will never be an incremental update to a record
3152 	//  in a protected scope.
3153 
3154 	// Change the scope vector on the rec.
3155 
3156 	newRec.setScopes(newScopes);
3157 
3158     }
3159 
3160     // Delete attributes matching attrTags.
3161 
3162     private void deleteAttributes(ServiceRecordInMemory rec,
3163 				  Vector attrTags)
3164 	throws ServiceLocationException {
3165 
3166 	// For each attribute, go through the tag vector and put attributes
3167 	// that do not match the tags into the new attribute vector.
3168 
3169 	Vector attrList = rec.getAttrList();
3170 
3171 	// If there are no attributes for this one, then simply return.
3172 
3173 	if (attrList.size() <= 0) {
3174 	    return;
3175 
3176 	}
3177 
3178 	int i, n = attrList.size();
3179 	Vector newAttrList = new Vector();
3180 	int len = attrTags.size();
3181 
3182 	for (i = 0; i < n; i++) {
3183 	    ServerAttribute attr =
3184 		(ServerAttribute)attrList.elementAt(i);
3185 	    AttributeString id = attr.idPattern;
3186 	    boolean deleteIt = false;
3187 
3188 	    int j;
3189 
3190 	    // Now check the tags.
3191 
3192 	    for (j = 0; j < len; j++) {
3193 		AttributePattern attrTag =
3194 		    (AttributePattern)attrTags.elementAt(j);
3195 
3196 		// If there's a match, mark for deletion.
3197 
3198 		if (attrTag.match(id)) {
3199 		    deleteIt = true;
3200 		    break;
3201 
3202 		}
3203 	    }
3204 
3205 	    if (!deleteIt) {
3206 		newAttrList.addElement(attr);
3207 	    }
3208 	}
3209 
3210 	// Replace the attribute vector in the record.
3211 
3212 	rec.setAttrList(newAttrList);
3213     }
3214 
3215     // Convert a vector of attribute tag strings to attribute pattern objects.
3216 
3217     private Vector stringVectorToAttributePattern(Vector tags, Locale locale)
3218 	throws ServiceLocationException {
3219 
3220 	// Takes care of findAttributes() case where no vector.
3221 
3222 	if (tags == null) {
3223 	    return null;
3224 
3225 	}
3226 
3227 	Vector v = new Vector();
3228 	int i, n = tags.size();
3229 
3230 	for (i = 0; i < n; i++) {
3231 	    String value = (String)tags.elementAt(i);
3232 
3233 	    AttributePattern tag =
3234 		new AttributePattern(value, locale);
3235 
3236 	    if (!v.contains(tag)) {
3237 		v.addElement(tag);
3238 
3239 	    }
3240 	}
3241 
3242 	return v;
3243     }
3244 
3245     //
3246     // Output of service store to log.
3247     //
3248 
3249     // Write record to config log file.
3250 
3251     private void
3252 	writeRecordToLog(SLPConfig conf, ServiceStore.ServiceRecord rec) {
3253 
3254 	Locale locale = rec.getLocale();
3255 	ServiceURL surl = rec.getServiceURL();
3256 	Vector scopes = rec.getScopes();
3257 	Vector attributes = rec.getAttrList();
3258 	long exTime = rec.getExpirationTime();
3259 	Hashtable urlSig = rec.getURLSignature();
3260 	Hashtable attrSig = rec.getAttrSignature();
3261 
3262 	conf.writeLogLine("ssim_dump_entry_start", new Object[0]);
3263 	conf.writeLogLine("ssim_dump_entry",
3264 			  new Object[] {
3265 	    locale,
3266 		surl.toString(),
3267 		Integer.toString(surl.getLifetime()),
3268 		Long.toString(((exTime - System.currentTimeMillis())/1000)),
3269 		surl.getServiceType(),
3270 		scopes,
3271 		attributes});
3272 
3273 	if (urlSig != null) {
3274 	    conf.writeLogLine("ssim_dump_urlsig",
3275 			      new Object[] {urlSig});
3276 
3277 	}
3278 
3279 	if (attrSig != null) {
3280 	    conf.writeLogLine("ssim_dump_attrsig",
3281 			      new Object[] {
3282 		attrSig});
3283 
3284 	}
3285 	conf.writeLogLine("ssim_entry_end", new Object[0]);
3286     }
3287 
3288     //
3289     // Utilities for dealing with service type/scope locale table.
3290     //
3291 
3292     // Bump up the number of registrations for this service type, scope and
3293     //  locale.
3294 
3295     private void
3296 	addTypeLocale(String type, String scope, String lang) {
3297 
3298 	String sstKey = makeScopeTypeKey(scope, type);
3299 
3300 	// Get any existing record.
3301 
3302 	Hashtable langTable = (Hashtable)sstLocales.get(sstKey);
3303 
3304 	// Insert a new one if none there.
3305 
3306 	if (langTable == null) {
3307 	    langTable = new Hashtable();
3308 
3309 	    sstLocales.put(sstKey, langTable);
3310 
3311 	}
3312 
3313 	// Look up locale.
3314 
3315 	Integer numRegs = (Integer)langTable.get(lang);
3316 
3317 	// Add a new one if none there, otherwise, bump up old.
3318 
3319 	if (numRegs == null) {
3320 	    numRegs = new Integer(1);
3321 
3322 	} else {
3323 	    numRegs = new Integer(numRegs.intValue() + 1);
3324 
3325 	}
3326 
3327 	// Put it back.
3328 
3329 	langTable.put(lang, numRegs);
3330 
3331     }
3332 
3333     // Bump down the number of registrations for this service type, scope,
3334     //  in all locales.
3335 
3336     private void deleteTypeLocale(String type, String scope, String lang) {
3337 
3338 	String sstKey = makeScopeTypeKey(scope, type);
3339 
3340 	// Get any existing record.
3341 
3342 	Hashtable langTable = (Hashtable)sstLocales.get(sstKey);
3343 
3344 	// If none there, then error. But this should have been caught
3345 	//  during deletion, so it's fatal.
3346 
3347 	Assert.slpassert(langTable != null,
3348 		      "ssim_ssttable_botch",
3349 		      new Object[] {
3350 	    type,
3351 		scope});
3352 
3353 	// Get the Integer object recording the number of registrations.
3354 
3355 	Integer numRegs = (Integer)langTable.get(lang);
3356 
3357 	Assert.slpassert(numRegs != null,
3358 		      "ssim_ssttable_lang_botch",
3359 		      new Object[] {
3360 	    lang,
3361 		type,
3362 		scope});
3363 
3364 	// Bump down by one, remove if zero.
3365 
3366 	numRegs = new Integer(numRegs.intValue() - 1);
3367 
3368 	if (numRegs.intValue() <= 0) {
3369 	    langTable.remove(lang);
3370 
3371 	    if (langTable.size() <= 0) {
3372 		sstLocales.remove(sstKey);
3373 
3374 	    }
3375 
3376 	    // Garbage collection.
3377 
3378 	    // Remove records from the scopeTypeLangTable,
3379 	    //  since there are no registrations left for this
3380 	    //  type/scope/locale.
3381 
3382 	    String stlKey =
3383 		makeScopeTypeLangKey(scope, type, lang);
3384 	    scopeTypeLangTable.remove(stlKey);
3385 
3386 	} else {
3387 
3388 	    // Put it back.
3389 
3390 	    langTable.put(lang, numRegs);
3391 
3392 	}
3393     }
3394 
3395     // Return REGS if the language is supported. Supported means that the
3396     //  there are some registrations of this service type in it or that
3397     //  there are none in any locale. Return NO_REGS if there are absolutely
3398     //  no registrations whatsoever, in any language. Return NO_REGS_IN_LOCALE
3399     //  if there are no registrations in that language but there are in
3400     //  others.
3401 
3402     private int
3403 	languageSupported(String type, Vector scopes, String lang) {
3404 
3405 	// Look through scope vector.
3406 
3407 	boolean otherLangRegs = false;
3408 	boolean sameLangRegs = false;
3409 	int i, n = scopes.size();
3410 
3411 	for (i = 0; i < n; i++) {
3412 	    String scope = (String)scopes.elementAt(i);
3413 	    String sstKey = makeScopeTypeKey(scope, type);
3414 
3415 	    // Get any existing record.
3416 
3417 	    Hashtable langTable = (Hashtable)sstLocales.get(sstKey);
3418 
3419 	    // If there are no regs, then check next scope.
3420 
3421 	    if (langTable == null) {
3422 		continue;
3423 
3424 	    }
3425 
3426 	    Object numRegs = langTable.get(lang);
3427 
3428 	    // Check whether there are other language regs
3429 	    //  or same language regs.
3430 
3431 	    if (numRegs == null) {
3432 		otherLangRegs = true;
3433 
3434 	    } else {
3435 		sameLangRegs = true;
3436 
3437 	    }
3438 	}
3439 
3440 	// Return appropriate code.
3441 
3442 	if (otherLangRegs == false &&
3443 	    sameLangRegs == false) {
3444 	    return NO_REGS;
3445 
3446 	} else if (otherLangRegs == true &&
3447 		   sameLangRegs == false) {
3448 	    return NO_REGS_IN_LOCALE;
3449 
3450 	} else {
3451 	    return REGS_IN_LOCALE;
3452 
3453 	}
3454     }
3455 
3456     //
3457     // Hash key calculations and hash table structuring.
3458     //
3459 
3460     // Return a key for type and scope.
3461 
3462     private String makeScopeTypeKey(String scope, String type) {
3463 	return scope + "/" + type;
3464 
3465     }
3466 
3467     // Make a hash key consisting of the scope and service type.
3468 
3469     final private String
3470 	makeScopeTypeLangKey(String scope,
3471 			     String serviceType,
3472 			     String lang) {
3473 
3474 	return scope + "/" + serviceType + "/" + lang;
3475     }
3476 
3477     // Return the key's scope.
3478 
3479     final private String keyScope(String key) {
3480 	int idx = key.indexOf('/');
3481 	String ret = "";
3482 
3483 	if (idx > 0) {
3484 	    ret = key.substring(0, idx);
3485 	}
3486 
3487 	return ret;
3488     }
3489 
3490 
3491     // Return the key's service type/NA.
3492 
3493     final private String keyServiceType(String key) {
3494 	int idx = key.indexOf('/');
3495 	String ret = "";
3496 	int len = key.length();
3497 
3498 	if (idx >= 0 && idx < len - 1) {
3499 	    ret = key.substring(idx+1, len);
3500 	}
3501 
3502 	// Parse off the final lang.
3503 
3504 	idx = ret.indexOf('/');
3505 
3506 	ret = ret.substring(0, idx);
3507 
3508 	return ret;
3509     }
3510 
3511     // Return true if the record is for an abstract type.
3512 
3513     final private boolean isAbstractTypeRecord(String sstKey) {
3514 	STLRecord rec = (STLRecord)scopeTypeLangTable.get(sstKey);
3515 
3516 	return rec.isAbstract;
3517 
3518     }
3519 
3520 }
3521