1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
|
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2001,2003 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
*/
// Parser.java: LDAP Parser for those service stores that need it.
// Author: James Kempf
// Created On: Mon Apr 27 08:11:08 1998
// Last Modified By: James Kempf
// Last Modified On: Mon Mar 1 08:29:36 1999
// Update Count: 45
//
package com.sun.slp;
import java.util.*;
import java.io.*;
/**
* The Parser class implements LDAP query parsing for ServiceStoreInMemory.
* It is an internal class because it must know about the internal
* structure of the hashtables.
*
* @author James Kempf
*/
abstract class Parser extends Object {
final private static char NONASCII_LOWER = '\u0080';
final private static char NONASCII_UPPER = '\uffff';
final static char EQUAL = '=';
final static char LESS = '<';
final static char GREATER = '>';
private final static char STAR = '*';
final static char PRESENT = STAR;
private final static char OPAREN = '(';
private final static char CPAREN = ')';
private final static char APPROX = '~';
private final static char NOT = '!';
private final static char AND = '&';
private final static char OR = '|';
private final static char SPACE = ' ';
/**
* Record for returning stuff to the service store.
*
* @author James Kempf
*/
static final class ParserRecord extends Object {
Hashtable services = new Hashtable();
Hashtable signatures = new Hashtable();
}
/**
* The QueryEvaluator interface evaluates a term in a query, given
* the attribute id, the operator, the object, and whether the
* term is currently under negation from a not operator. Only those
* ServiceStore implemenations that want to use the Parser
* class to perform query parsing must provide this.
*
* @author James Kempf
*/
interface QueryEvaluator {
/**
* Evaluate the query, storing away the services that match.
*
* @param tag The attribute tag for the term.
* @param op The term operator.
* @param pattern the operand of the term.
* @param invert True if the results of the comparison should be
* inverted due to a not operator.
* @param returns Hashtable for the returns. The returns are
* structured exactly like the hashtable
* returned from findServices().
* @return True if the term matched, false if not.
*/
boolean evaluate(AttributeString tag,
char op,
Object pattern,
boolean invert,
ParserRecord returns)
throws ServiceLocationException;
}
/**
* Parse a query and incrementally evaluate.
*
* @param urlLevel Hashtable of langlevel hashtables containing
* registrations for the service type and scope.
* @param query The query. Escapes have not yet been processed.
* @param ret Vector for returned records.
* @param locale Locale in which to interpret query strings.
* @param ret ParserRecord in which to return the results.
*/
static void
parseAndEvaluateQuery(String query,
Parser.QueryEvaluator ev,
Locale locale,
ParserRecord ret)
throws ServiceLocationException {
// Create and initialize lexical analyzer.
StreamTokenizer tk = new StreamTokenizer(new StringReader(query));
tk.resetSyntax(); // make all chars ordinary...
tk.wordChars('\177','\177'); // treat controls as part of tokens
tk.wordChars('\000', SPACE);
tk.ordinaryChar(NOT); // 'NOT' operator
tk.wordChars('"', '%');
tk.ordinaryChar(AND); // 'AND' operator
tk.wordChars('\'', '\'');
tk.ordinaryChar(OPAREN); // filter grouping
tk.ordinaryChar(CPAREN);
tk.ordinaryChar(STAR); // present operator
tk.wordChars('+', '{');
tk.ordinaryChar(OR); // 'OR' operator
tk.wordChars('}', '~');
tk.ordinaryChar(EQUAL); // comparision operator
tk.ordinaryChar(LESS); // less operator
tk.ordinaryChar(GREATER); // greater operator
tk.ordinaryChar(APPROX); // approx operator
// Begin parsing.
try {
ParserRecord rec = parseFilter(tk, ev, locale, false, true);
// Throw exception if anything occurs after the
// parsed expression.
if (tk.nextToken() != StreamTokenizer.TT_EOF) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"par_char_closing",
new Object[] {query});
}
// Merge in returns. Use OR operator so all returned
// values are merged in.
mergeQueryReturns(ret, rec, OR);
} catch (IOException ex) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"par_syn_err",
new Object[] {query});
}
}
//
// Routines for dealing with parse returns record.
//
// Merge source to target. The target has already
// been precharged with ones that must match
// if the op is AND. If it's OR, then simply
// stuff them in.
private static boolean
mergeQueryReturns(ParserRecord target,
ParserRecord source,
char op) {
Hashtable targetServices = target.services;
Hashtable sourceServices = source.services;
boolean eval;
if (op == AND) {
eval = mergeTablesWithAnd(targetServices, sourceServices);
} else {
eval = mergeTablesWithOr(targetServices, sourceServices);
}
Hashtable targetSigs = target.signatures;
Hashtable sourceSigs = source.signatures;
if (op == AND) {
mergeTablesWithAnd(targetSigs, sourceSigs);
} else {
mergeTablesWithOr(targetSigs, sourceSigs);
}
return eval;
}
// Merge tables by removing anything from target that isn't in source.
private static boolean mergeTablesWithAnd(Hashtable target,
Hashtable source) {
Enumeration en = target.keys();
// Remove any from target that aren't in source.
while (en.hasMoreElements()) {
Object tkey = en.nextElement();
if (source.get(tkey) == null) {
target.remove(tkey);
}
}
// If there's nothing left, return false to indicate no further
// evaluation needed.
if (target.size() <= 0) {
return false;
}
return true;
}
// Merge tables by adding everything from source into target.
private static boolean mergeTablesWithOr(Hashtable target,
Hashtable source) {
Enumeration en = source.keys();
while (en.hasMoreElements()) {
Object skey = en.nextElement();
target.put(skey, source.get(skey));
}
return true;
}
//
// Parsing for various productions.
//
// Parse the filter production.
private static ParserRecord
parseFilter(StreamTokenizer tk,
Parser.QueryEvaluator ev,
Locale locale,
boolean invert,
boolean eval)
throws ServiceLocationException, IOException {
ParserRecord ret = null;
int tok = tk.nextToken();
// Check for opening paren.
if (tok != OPAREN) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"par_init_par",
new Object[0]);
}
// Parse inside.
tok = tk.nextToken();
// Check for a logical operator.
if (tok == AND || tok == OR) {
ret = parseFilterlist(tk, ev, locale, (char)tok, invert, eval);
} else if (tok == NOT) {
ret = parseFilter(tk, ev, locale, !invert, eval);
} else if (tok == StreamTokenizer.TT_WORD) {
tk.pushBack();
ret = parseItem(tk, ev, locale, invert, eval);
} else {
// Since we've covered the ASCII character set, the only other
// thing that could be here is a nonASCII character. We push it
// back and deal with it in parseItem().
tk.pushBack();
ret = parseItem(tk, ev, locale, invert, eval);
}
tok = tk.nextToken();
// Check for closing paren.
if (tok != CPAREN) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"par_final_par",
new Object[0]);
}
return ret;
}
// Parse a filterlist production.
private static ParserRecord
parseFilterlist(StreamTokenizer tk,
Parser.QueryEvaluator ev,
Locale locale,
char op,
boolean invert,
boolean eval)
throws ServiceLocationException, IOException {
boolean match;
ParserRecord mrex = null;
// Parse through the list of filters.
do {
ParserRecord prex = null;
if (op == AND) {
prex = parseFilter(tk, ev, locale, invert, eval);
} else {
prex = parseFilter(tk, ev, locale, invert, eval);
}
// We need to start off with something.
if (mrex == null) {
mrex = prex;
} else {
// Merge in returns.
eval = mergeQueryReturns(mrex, prex, op);
}
// Look for ending paren.
int tok = tk.nextToken();
tk.pushBack();
if (tok == CPAREN) {
return mrex;
}
} while (true);
}
// Parse item.
private static ParserRecord
parseItem(StreamTokenizer tk,
Parser.QueryEvaluator ev,
Locale locale,
boolean invert,
boolean eval)
throws ServiceLocationException, IOException {
ParserRecord prex = new ParserRecord();
AttributeString attr = parseAttr(tk, locale);
char op = parseOp(tk);
Object value = null;
// If operator is PRESENT, then check whether
// it's not really a wildcarded value. If the next
// token isn't a closing paren, then it's
// a wildcarded value.
if (op == PRESENT) {
int tok = tk.nextToken();
tk.pushBack(); // ...in any event...
if ((char)tok != CPAREN) { // It's a wildcarded pattern...
op = EQUAL;
value = parseValue(tk, locale);
// Need to convert to a wildcarded pattern. Regardless
// of type, since wildcard makes the type be a
// string.
value =
new AttributePattern(PRESENT + value.toString(), locale);
}
} else {
value = parseValue(tk, locale);
}
// Check for inappropriate pattern.
if (value instanceof AttributePattern &&
((AttributePattern)value).isWildcarded() &&
op != EQUAL) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"par_wild_op",
new Object[] {new Character(op)});
}
// Check for inappropriate boolean.
if ((value instanceof Boolean ||
value instanceof Opaque) &&
(op == GREATER || op == LESS)) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"par_bool_op",
new Object[] {new Character(op)});
}
// Check for wrong operator with keyword.
if ((value == null || value.toString().length() <= 0) &&
op != PRESENT) {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"par_key_op",
new Object[] {new Character(op)});
}
if (eval) {
/*
* Try and evaluate the query. If the evaluation failed and the
* value was an Integer or Boolean try again after converting the
* value to a String. This is because the value in the query will
* be converted to an Integer or Boolean in preference to a String
* even though the query starts out as a String. Hence when an
* attribute is registered with a String value that can equally be
* parsed as a valid Integer or Boolean value the String will
* almost always be parsed as an Integer or Boolean. This results
* in the failing of the initial type check when performing the
* query. By converting the value to a String there is another shot
* at fulfulling the query.
*/
if (!ev.evaluate(attr, op, value, invert, prex) &&
!(value instanceof AttributeString)) {
ev.evaluate(attr,
op,
new AttributeString(
value.toString().trim(),
locale),
invert,
prex);
}
}
return prex;
}
// Parse attribute tag.
private static AttributeString parseAttr(StreamTokenizer tk, Locale locale)
throws ServiceLocationException, IOException {
String str = parsePotentialNonASCII(tk);
str =
ServiceLocationAttribute.unescapeAttributeString(str, true);
return new AttributeString(str, locale);
}
// Parse attribute operator.
private static char parseOp(StreamTokenizer tk)
throws ServiceLocationException, IOException {
int tok = tk.nextToken();
// Identify operator
switch (tok) {
case EQUAL:
// Is it present?
tok = tk.nextToken();
if (tok == STAR) {
return PRESENT;
} else {
tk.pushBack();
return EQUAL;
}
case APPROX: case GREATER: case LESS:
// Need equals.
if (tk.nextToken() != EQUAL) {
break;
}
if (tok == APPROX) {
tok = EQUAL;
}
return (char)tok;
default:
break;
}
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"par_comp_op",
new Object[0]);
}
// Parse expression value.
private static Object parseValue(StreamTokenizer tk, Locale locale)
throws ServiceLocationException, IOException {
StringBuffer buf = new StringBuffer();
// Parse until the next closing paren.
do {
int tok = tk.nextToken();
if (tok == CPAREN) {
tk.pushBack();
Object o =
ServiceLocationAttribute.evaluate(buf.toString().trim());
if (o instanceof String) {
o = new AttributePattern((String)o, locale);
} else if (o instanceof byte[]) {
o = new Opaque((byte[])o);
}
return o;
} else if (tok != StreamTokenizer.TT_EOF) {
if (tok == StreamTokenizer.TT_WORD) {
buf.append(tk.sval);
} else if (tok == StreamTokenizer.TT_NUMBER) {
Assert.slpassert(false,
"par_ntok",
new Object[0]);
} else {
buf.append((char)tok);
}
} else {
throw
new ServiceLocationException(
ServiceLocationException.PARSE_ERROR,
"par_qend",
new Object[0]);
}
} while (true);
}
// NonASCII characters may be in the string. StreamTokenizer
// can't handle them as part of words, so we need to resort to
// this loop to handle it.
private static String parsePotentialNonASCII(StreamTokenizer tk)
throws IOException {
StringBuffer buf = new StringBuffer();
do {
int tok = tk.nextToken();
if (tok == StreamTokenizer.TT_WORD) {
buf.append(tk.sval);
} else if (((char)tok >= NONASCII_LOWER) &&
((char)tok <= NONASCII_UPPER)) {
buf.append((char)tok);
} else {
tk.pushBack();
break;
}
} while (true);
return buf.toString();
}
}
|