1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.http;
20
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.StringTokenizer;
27
28 import org.eclipse.jetty.util.LazyList;
29 import org.eclipse.jetty.util.StringMap;
30 import org.eclipse.jetty.util.URIUtil;
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 public class PathMap<O> extends HashMap<String,O>
64 {
65
66 private static String __pathSpecSeparators = ":,";
67
68
69
70
71
72
73
74
75 public static void setPathSpecSeparators(String s)
76 {
77 __pathSpecSeparators=s;
78 }
79
80
81 final StringMap<MappedEntry<O>> _prefixMap=new StringMap<>();
82 final StringMap<MappedEntry<O>> _suffixMap=new StringMap<>();
83 final StringMap<MappedEntry<O>> _exactMap=new StringMap<>();
84
85 List _defaultSingletonList=null;
86 MappedEntry<O> _prefixDefault=null;
87 MappedEntry<O> _default=null;
88 final Set _entrySet;
89 boolean _nodefault=false;
90
91
92 public PathMap()
93 {
94 this(11);
95 }
96
97
98 public PathMap(boolean noDefault)
99 {
100 this(11, noDefault);
101 }
102
103
104 public PathMap(int capacity)
105 {
106 this(capacity, false);
107 }
108
109
110 private PathMap(int capacity, boolean noDefault)
111 {
112 super(capacity);
113 _nodefault=noDefault;
114 _entrySet=entrySet();
115 }
116
117
118
119
120 public PathMap(Map<String, ? extends O> m)
121 {
122 putAll(m);
123 _entrySet=entrySet();
124 }
125
126
127
128
129
130
131
132 @Override
133 public O put(String pathSpec, O object)
134 {
135 if ("".equals(pathSpec.trim()))
136 {
137 MappedEntry<O> entry = new MappedEntry<>("",object);
138 entry.setMapped("");
139 _exactMap.put("", entry);
140 return super.put("", object);
141 }
142
143 StringTokenizer tok = new StringTokenizer(pathSpec,__pathSpecSeparators);
144 O old =null;
145
146 while (tok.hasMoreTokens())
147 {
148 String spec=tok.nextToken();
149
150 if (!spec.startsWith("/") && !spec.startsWith("*."))
151 throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'");
152
153 old = super.put(spec,object);
154
155
156 MappedEntry<O> entry = new MappedEntry<>(spec,object);
157
158 if (entry.getKey().equals(spec))
159 {
160 if (spec.equals("/*"))
161 _prefixDefault=entry;
162 else if (spec.endsWith("/*"))
163 {
164 String mapped=spec.substring(0,spec.length()-2);
165 entry.setMapped(mapped);
166 _prefixMap.put(mapped,entry);
167 _exactMap.put(mapped,entry);
168 _exactMap.put(spec.substring(0,spec.length()-1),entry);
169 }
170 else if (spec.startsWith("*."))
171 _suffixMap.put(spec.substring(2),entry);
172 else if (spec.equals(URIUtil.SLASH))
173 {
174 if (_nodefault)
175 _exactMap.put(spec,entry);
176 else
177 {
178 _default=entry;
179 _defaultSingletonList=
180 Collections.singletonList(_default);
181 }
182 }
183 else
184 {
185 entry.setMapped(spec);
186 _exactMap.put(spec,entry);
187 }
188 }
189 }
190
191 return old;
192 }
193
194
195
196
197
198
199 public O match(String path)
200 {
201 MappedEntry<O> entry = getMatch(path);
202 if (entry!=null)
203 return entry.getValue();
204 return null;
205 }
206
207
208
209
210
211
212
213 public MappedEntry<O> getMatch(String path)
214 {
215 if (path==null)
216 return null;
217
218 int l=path.length();
219
220 MappedEntry<O> entry=null;
221
222
223 if (l == 1 && path.charAt(0)=='/')
224 {
225 entry = _exactMap.get("");
226 if (entry != null)
227 return entry;
228 }
229
230
231 entry=_exactMap.get(path,0,l);
232 if (entry!=null)
233 return entry;
234
235
236 int i=l;
237 while((i=path.lastIndexOf('/',i-1))>=0)
238 {
239 entry=_prefixMap.get(path,0,i);
240 if (entry!=null)
241 return entry;
242 }
243
244
245 if (_prefixDefault!=null)
246 return _prefixDefault;
247
248
249 i=0;
250 while ((i=path.indexOf('.',i+1))>0)
251 {
252 entry=_suffixMap.get(path,i+1,l-i-1);
253 if (entry!=null)
254 return entry;
255 }
256
257
258 return _default;
259 }
260
261
262
263
264
265
266
267 public Object getLazyMatches(String path)
268 {
269 MappedEntry entry;
270 Object entries=null;
271
272 if (path==null)
273 return LazyList.getList(entries);
274
275 int l=path.length();
276
277
278 entry=_exactMap.get(path,0,l);
279 if (entry!=null)
280 entries=LazyList.add(entries,entry);
281
282
283 int i=l-1;
284 while((i=path.lastIndexOf('/',i-1))>=0)
285 {
286 entry=_prefixMap.get(path,0,i);
287 if (entry!=null)
288 entries=LazyList.add(entries,entry);
289 }
290
291
292 if (_prefixDefault!=null)
293 entries=LazyList.add(entries,_prefixDefault);
294
295
296 i=0;
297 while ((i=path.indexOf('.',i+1))>0)
298 {
299 entry=_suffixMap.get(path,i+1,l-i-1);
300 if (entry!=null)
301 entries=LazyList.add(entries,entry);
302 }
303
304
305 if (_default!=null)
306 {
307
308 if (entries==null)
309 return _defaultSingletonList;
310
311 entries=LazyList.add(entries,_default);
312 }
313
314 return entries;
315 }
316
317
318
319
320
321
322
323 public List getMatches(String path)
324 {
325 return LazyList.getList(getLazyMatches(path));
326 }
327
328
329
330
331
332
333
334 public boolean containsMatch(String path)
335 {
336 MappedEntry match = getMatch(path);
337 return match!=null && !match.equals(_default);
338 }
339
340
341 @Override
342 public O remove(Object pathSpec)
343 {
344 if (pathSpec!=null)
345 {
346 String spec=(String) pathSpec;
347 if (spec.equals("/*"))
348 _prefixDefault=null;
349 else if (spec.endsWith("/*"))
350 {
351 _prefixMap.remove(spec.substring(0,spec.length()-2));
352 _exactMap.remove(spec.substring(0,spec.length()-1));
353 _exactMap.remove(spec.substring(0,spec.length()-2));
354 }
355 else if (spec.startsWith("*."))
356 _suffixMap.remove(spec.substring(2));
357 else if (spec.equals(URIUtil.SLASH))
358 {
359 _default=null;
360 _defaultSingletonList=null;
361 }
362 else
363 _exactMap.remove(spec);
364 }
365 return super.remove(pathSpec);
366 }
367
368
369 @Override
370 public void clear()
371 {
372 _exactMap.clear();
373 _prefixMap.clear();
374 _suffixMap.clear();
375 _default=null;
376 _defaultSingletonList=null;
377 super.clear();
378 }
379
380
381
382
383
384 public static boolean match(String pathSpec, String path)
385 throws IllegalArgumentException
386 {
387 return match(pathSpec, path, false);
388 }
389
390
391
392
393
394 public static boolean match(String pathSpec, String path, boolean noDefault)
395 throws IllegalArgumentException
396 {
397 char c = pathSpec.charAt(0);
398 if (c=='/')
399 {
400 if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path))
401 return true;
402
403 if(isPathWildcardMatch(pathSpec, path))
404 return true;
405 }
406 else if (c=='*')
407 return path.regionMatches(path.length()-pathSpec.length()+1,
408 pathSpec,1,pathSpec.length()-1);
409 return false;
410 }
411
412
413 private static boolean isPathWildcardMatch(String pathSpec, String path)
414 {
415
416 int cpl=pathSpec.length()-2;
417 if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl))
418 {
419 if (path.length()==cpl || '/'==path.charAt(cpl))
420 return true;
421 }
422 return false;
423 }
424
425
426
427
428
429
430 public static String pathMatch(String pathSpec, String path)
431 {
432 char c = pathSpec.charAt(0);
433
434 if (c=='/')
435 {
436 if (pathSpec.length()==1)
437 return path;
438
439 if (pathSpec.equals(path))
440 return path;
441
442 if (isPathWildcardMatch(pathSpec, path))
443 return path.substring(0,pathSpec.length()-2);
444 }
445 else if (c=='*')
446 {
447 if (path.regionMatches(path.length()-(pathSpec.length()-1),
448 pathSpec,1,pathSpec.length()-1))
449 return path;
450 }
451 return null;
452 }
453
454
455
456
457
458 public static String pathInfo(String pathSpec, String path)
459 {
460 if ("".equals(pathSpec))
461 return path;
462
463 char c = pathSpec.charAt(0);
464
465 if (c=='/')
466 {
467 if (pathSpec.length()==1)
468 return null;
469
470 boolean wildcard = isPathWildcardMatch(pathSpec, path);
471
472
473 if (pathSpec.equals(path) && !wildcard)
474 return null;
475
476 if (wildcard)
477 {
478 if (path.length()==pathSpec.length()-2)
479 return null;
480 return path.substring(pathSpec.length()-2);
481 }
482 }
483 return null;
484 }
485
486
487
488
489
490
491
492
493
494 public static String relativePath(String base,
495 String pathSpec,
496 String path )
497 {
498 String info=pathInfo(pathSpec,path);
499 if (info==null)
500 info=path;
501
502 if( info.startsWith( "./"))
503 info = info.substring( 2);
504 if( base.endsWith( URIUtil.SLASH))
505 if( info.startsWith( URIUtil.SLASH))
506 path = base + info.substring(1);
507 else
508 path = base + info;
509 else
510 if( info.startsWith( URIUtil.SLASH))
511 path = base + info;
512 else
513 path = base + URIUtil.SLASH + info;
514 return path;
515 }
516
517
518
519
520 public static class MappedEntry<O> implements Map.Entry<String,O>
521 {
522 private final String key;
523 private final O value;
524 private String mapped;
525
526 MappedEntry(String key, O value)
527 {
528 this.key=key;
529 this.value=value;
530 }
531
532 @Override
533 public String getKey()
534 {
535 return key;
536 }
537
538 @Override
539 public O getValue()
540 {
541 return value;
542 }
543
544 @Override
545 public O setValue(O o)
546 {
547 throw new UnsupportedOperationException();
548 }
549
550 @Override
551 public String toString()
552 {
553 return key+"="+value;
554 }
555
556 public String getMapped()
557 {
558 return mapped;
559 }
560
561 void setMapped(String mapped)
562 {
563 this.mapped = mapped;
564 }
565 }
566 }