Project

General

Profile

ScopedDictionary.java

Greg Shah, 07/06/2020 12:55 PM

Download (40.3 KB)

 
1
/*
2
** Module   : ScopedDictionary.java
3
** Abstract : Provides multiscoped dictionary functionality.
4
**
5
** Copyright (c) 2004-2020, Golden Code Development Corporation.
6
**
7
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
8
** 001 ECF 20051028   @23220 Abstracted from ScopedSymbolDictionary, which
9
**                           is now a subclass of this class. This was
10
**                           necessary to provide the base functionality
11
**                           of this class (scoped mapping) without the
12
**                           assumption that all keys are of type String.
13
**                           This class uses Object for its entry keys.
14
** 002 NVS 20051207   @23672 Added new method getScopeAt(int) which gives
15
**                           access to the user objects at any depth.
16
** 003 ECF 20060208   @24705 Added values() and entrySet() methods. These
17
**                           are analogs to the keySet() methods to round
18
**                           out the map-like API.
19
** 004 NVS 20070118   @31892 Added new methods to replace the extra object
20
**                           in the current or any other scope.
21
** 005 ECF 20071029   @35630 Implemented generics. Intentionally left the
22
**                           'extra' data as Object for now.
23
** 006 GES 20080308   @37669 Added writer support to dump methods so that
24
**                           I18N issues can be improved.
25
** 007 SIY 20080904   @39716 Minor cleanups.
26
** 008 ECF 20090115   @41159 Added new methods. deleteScope(boolean)
27
**                           optionally propagates entries up when popping
28
**                           a scope; clear() removes all entries from all
29
**                           scopes (but leaves the scopes intact);
30
**                           removeEntryAtScope(K, int) removes a specific
31
**                           entry from a specific scope;
32
**                           getDictionaryAtScope(int) returns a map for a
33
**                           given scope to enable direct manipulation.
34
** 009 ECF 20090218   @41335 Major memory improvement. Dictionary for each
35
**                           node is now created lazily for operations
36
**                           which write to the dictionary. For operations
37
**                           which simply read, an immutable, empty map is
38
**                           used in nodes where no dictionary yet exists.
39
**                           Also reduced the starting size of the map.
40
** 010 GES 20111205          Exposed processKey() as public.
41
** 011 SVL 20131028          Added addEntryAt().
42
** 012 ECF 20150222          Made adding entries slightly more efficient, by avoiding an
43
**                           unnecessary key check.
44
** 013 ECF 20150322          Rewrote the implementation of the values methods to eliminate the
45
**                           copying of data to new collection objects, to improve performance.
46
** 014 ECF 20160429          Added removeEntryThroughScope.
47
** 015 ECF 20160607          Added variant of lookup which takes starting scope.
48
** 016 CA  20160728          Added copyAll API, to allow a dictionary duplication; if the 
49
**                           keepGlobal flag is true, then the global scope is shared between the
50
**                           source and the copy; otherwise, it's duplicated.
51
** 017 CA  20161010          Added apply, an API which will execute the given code for all 
52
**                           the key's values, in all scopes, starting from innermost to global,
53
**                           until the caller decides to terminate the search.
54
** 018 ECF 20180311          Added reverseLookup variant to begin search at an arbitrary scope.
55
** 019 ECF 20190302          dumpScope now shows depth.
56
** 020 ECF 20190322          Optimized getDictionaryAtScope for the common case.
57
** 021 GES 20200706          Optimized all at scope operations to index into the list instead of using an
58
**                           iterator.
59
*/
60

    
61
/*
62
** This program is free software: you can redistribute it and/or modify
63
** it under the terms of the GNU Affero General Public License as
64
** published by the Free Software Foundation, either version 3 of the
65
** License, or (at your option) any later version.
66
**
67
** This program is distributed in the hope that it will be useful,
68
** but WITHOUT ANY WARRANTY; without even the implied warranty of
69
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
70
** GNU Affero General Public License for more details.
71
**
72
** You may find a copy of the GNU Affero GPL version 3 at the following
73
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
74
** 
75
** Additional terms under GNU Affero GPL version 3 section 7:
76
** 
77
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
78
**   terms apply to the works covered under the License.  These additional terms
79
**   are non-permissive additional terms allowed under Section 7 of the GNU
80
**   Affero GPL version 3 and may not be removed by you.
81
** 
82
**   0. Attribution Requirement.
83
** 
84
**     You must preserve all legal notices or author attributions in the covered
85
**     work or Appropriate Legal Notices displayed by works containing the covered
86
**     work.  You may not remove from the covered work any author or developer
87
**     credit already included within the covered work.
88
** 
89
**   1. No License To Use Trademarks.
90
** 
91
**     This license does not grant any license or rights to use the trademarks
92
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
93
**     of Golden Code Development Corporation. You are not authorized to use the
94
**     name Golden Code, FWD, or the names of any author or contributor, for
95
**     publicity purposes without written authorization.
96
** 
97
**   2. No Misrepresentation of Affiliation.
98
** 
99
**     You may not represent yourself as Golden Code Development Corporation or FWD.
100
** 
101
**     You may not represent yourself for publicity purposes as associated with
102
**     Golden Code Development Corporation, FWD, or any author or contributor to
103
**     the covered work, without written authorization.
104
** 
105
**   3. No Misrepresentation of Source or Origin.
106
** 
107
**     You may not represent the covered work as solely your work.  All modified
108
**     versions of the covered work must be marked in a reasonable way to make it
109
**     clear that the modified work is not originating from Golden Code Development
110
**     Corporation or FWD.  All modified versions must contain the notices of
111
**     attribution required in this license.
112
*/
113

    
114
package com.goldencode.p2j.util;
115

    
116
import java.io.*;
117
import java.util.*;
118
import java.util.function.*;
119

    
120
/**
121
 * Implements a symbol dictionary with multiple levels of scope.  Entries can
122
 * be added to, removed from and queried in the dictionary.  Each entry
123
 * consists of an <code>Object</code> key and an optional, user-defined
124
 * object that can store arbitrary user data.  As a result of the lookup
125
 * process, if an entry is found, the user-defined object is returned.
126
 * <p>
127
 * When entries are added, they are stored in a flat list called a scope.
128
 * Each scope can store a set of entries where each symbol key must be unique.
129
 * The user has control over how many scopes exist.  By default, no scopes
130
 * are available.  The user of this class is responsible for adding scopes
131
 * using the <code>addScope</code> method.  When the entries stored in the
132
 * current scope should no longer be available, the scope can be removed
133
 * using the <code>deleteScope</code> method.  This multilevel scope
134
 * approach can be thought of as a stack of scopes where the current scope
135
 * is the top of the stack and the bottom of the stack has a special scope
136
 * that is considered "global".  Entries can be added to the current scope
137
 * or to the global scope using the <code>addEntry</code> method.
138
 * <p>
139
 * Since the global scope has a special meaning, a convenience constructor
140
 * is provided that automatically creates the global scope.
141
 * <p>
142
 * Keys must only be unique within a single scope.  If the same key exists
143
 * in multiple scopes, the instance found closest to the current scope (top
144
 * of stack) is the one that is found on lookup.  Likewise, when an entry
145
 * is deleted using <code>deleteEntry</code>, the topmost entry is the one
146
 * removed.
147
 * <p>
148
 * @param   <K>
149
 *          Dictionary key type
150
 * @param   <V>
151
 *          Dictionary value type
152
 */
153
public class ScopedDictionary<K, V>
154
{
155
   /** Implements stackable scopes */
156
   private LinkedList<Node<K, V>> scopes = new LinkedList<>();
157
   
158
   /**
159
    * Default constructor.
160
    */
161
   public ScopedDictionary()
162
   {
163
   }
164
   
165
   /**
166
    * Convenience constructor that automatically creates the global scope.
167
    *
168
    * @param  extra
169
    *         Object to be associated with the global scope.
170
    */
171
   public ScopedDictionary(Object extra)
172
   {
173
      addScope(extra);
174
   }
175
   
176
   /**
177
    * Clear this dictionary and copy all content from the source.  The global scope will be either
178
    * duplicated or kept as a standalone reference, to be shared among copies.
179
    * 
180
    * @param    source
181
    *           The source dictionary.
182
    * @param    keepGlobal
183
    *           When <code>true</code>, the global dictionary is exposed directly to this copy;
184
    *           otherwise is duplicated.
185
    */
186
   public void copyAll(ScopedDictionary<K, V> source, boolean keepGlobal)
187
   {
188
      scopes.clear();
189
      
190
      Node<K, V> global = source.scopes.getFirst();
191
      
192
      for (Node<K, V> node : source.scopes)
193
      {
194
         Node<K, V> newNode = null;
195
         
196
         if (global != null && keepGlobal && node == global)
197
         {
198
            newNode = global;
199
            global = null;
200
         }
201
         else
202
         {
203
            newNode = node.clone();
204
         }
205
         
206
         scopes.add(newNode);
207
      }
208
   }
209

    
210
   /**
211
    * Creates a new scope and associates a user-defined object with it.
212
    * The new scope appears on top of the stack.
213
    *
214
    * @param  extra
215
    *         Object to be associated with new scope.
216
    */
217
   public void addScope(Object extra)
218
   {
219
      scopes.addLast(new Node<K, V>(extra));
220
   }
221
   
222
   /**
223
    * Deletes the current scope which is the one on the top of the stack.
224
    * It gets popped off the stack and all entries defined in that scope
225
    * are no longer accessible.
226
    */
227
   public void deleteScope()
228
   {
229
      deleteScope(false);
230
   }
231
   
232
   /**
233
    * Deletes the current scope which is the one on the top of the stack, and
234
    * optionally propagates all entries defined in that scope to the next
235
    * scope on the stack.  Any existing entries in the next scope, which have
236
    * the same keys as entries in the deleted scope, are overwritten.
237
    * 
238
    * @param   propagate
239
    *          <code>true</code> to propagate entries up to the next scope;
240
    *          <code>false</code> to simply remove those entries.
241
    */
242
   public void deleteScope(boolean propagate)
243
   {
244
      Node<K, V> removed = scopes.removeLast();
245
      if (propagate)
246
      {
247
         Node<K, V> next = scopes.getLast();
248
         if (next != null)
249
         {
250
            Map<K, V> oldDict = removed.getDictionary(false);
251
            if (!oldDict.isEmpty())
252
            {
253
               Map<K, V> dict = next.getDictionary(true);
254
               dict.putAll(oldDict);
255
            }
256
         }
257
      }
258
   }
259
   
260
   /**
261
    * Accesses the user object in the current scope.
262
    *
263
    * @return  Object associated with the current dictionary.
264
    */
265
   public Object getScope()
266
   {
267
      if (scopes.size() > 0)
268
      {
269
         return scopes.getLast().getExtra();
270
      }
271
      
272
      return null;
273
   }
274
   
275
   /**
276
    * Replaces the user object in the current scope.
277
    *
278
    * @param   extra
279
    *          Any object that will be associated with this scope.
280
    */
281
   public void setScope(Object extra)
282
   {
283
      if (scopes.size() > 0)
284
      {
285
         scopes.getLast().setExtra(extra);
286
      }
287
   }
288
   
289
   /**
290
    * Accesses the user object in the scope specified. The current scope is
291
    * the one on the top of the stack.
292
    *
293
    * @param   scope
294
    *          The depth (in number of scopes from the top of the stack) from
295
    *          which the value must be gotten. Use -1 to reference the global
296
    *          scope.
297
    *
298
    * @return  Object associated with the specified scope.
299
    */
300
   public Object getScopeAt(int scope)
301
   {
302
      Node<K, V> node = getNodeAt(scope);
303
      
304
      return (node == null) ? null : node.getExtra();
305
   }
306
   
307
   /**
308
    * Replaces the user object in the scope specified. The current scope is
309
    * the one on the top of the stack.
310
    *
311
    * @param   scope
312
    *          The depth (in number of scopes from the top of the stack) from
313
    *          which the value must be gotten. Use -1 to reference the global
314
    *          scope.
315
    * @param   extra
316
    *          Any object that will be associated with this scope.
317
    */
318
   public void setScopeAt(int scope, Object extra)
319
   {
320
      Node<K, V> node = getNodeAt(scope);
321
      
322
      if (node != null)
323
      {
324
         node.setExtra(extra);
325
      }
326
   }
327
   
328
   /**
329
    * Returns the stack depth.
330
    *
331
    * @return  Number of scopes currently on the stack.
332
    */
333
   public int size()
334
   {
335
      return scopes.size();
336
   }
337
   
338
   /**
339
    * Adds a value to the current or global scope or replaces its
340
    * user-defined object. The current scope is the one on the top of the
341
    * stack. The global scope is always the first scope created on the stack.
342
    * If only one scope is on the stack, both refer to the same scope.
343
    *
344
    * @param   global 
345
    *          Specifies the target scope. <code>false</code> specifies the
346
    *          current scope, and <code>true</code> specifies the global
347
    *          scope.
348
    * @param   key
349
    *          The lookup key.
350
    * @param   value
351
    *          The stored value which may be any object.
352
    *
353
    * @return  <code>true</code> if the entry has been added,
354
    *          <code>false</code> if its value has been replaced.
355
    */
356
   public boolean addEntry(boolean global, K key, V value)
357
   {
358
      Node<K, V> n = null;
359
      Map<K, V> dictionary = null;
360
      boolean result = false;
361
      
362
      if (global)
363
      {
364
         n = scopes.getFirst();
365
      }
366
      else
367
      {
368
         n = scopes.getLast();
369
      }
370
      
371
      dictionary = n.getDictionary(true);
372
      
373
      key = processKey(key);
374
      result = (dictionary.put(key, value) == null);
375
      
376
      return result;
377
   }
378
   
379
   /**
380
    * Adds a value to the scope at the specified depth or replaces its user-defined object.
381
    *
382
    * @param   depth
383
    *          zero-based index of the target scope, starting from the outermost scope.
384
    * @param   key
385
    *          The lookup key.
386
    * @param   value
387
    *          The stored value which may be any object.
388
    *
389
    * @return  <code>true</code> if the entry has been added,
390
    *          <code>false</code> if its value has been replaced.
391
    *
392
    * @throws  ArrayIndexOutOfBoundsException
393
    *          if invalid <code>depth</code> is specified.
394
    */
395
   public boolean addEntryAt(int depth, K key, V value)
396
   throws ArrayIndexOutOfBoundsException
397
   {
398
      Node<K, V> n = null;
399
      Map<K, V> dictionary = null;
400
      boolean result = false;
401
      
402
      n = scopes.get(depth);
403
      
404
      dictionary = n.getDictionary(true);
405
      
406
      key = processKey(key);
407
      result = (dictionary.put(key, value) == null);
408
      
409
      return result;
410
   }
411
   
412
   /**
413
    * Searches all scopes from the top of the stack down and deletes
414
    * the first found occurrence of the target entry.
415
    *
416
    * @param   key
417
    *          The lookup key.
418
    *
419
    * @return  <code>true</code> if the entry has been deleted.
420
    */
421
   public boolean deleteEntry(K key)
422
   {
423
      ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
424
      Map<K, V> dictionary = null;
425
      
426
      key = processKey(key);
427
      
428
      while (li.hasPrevious())
429
      {
430
         dictionary = li.previous().getDictionary(false);
431
         if (dictionary.remove(key) != null)
432
         {
433
            return true;
434
         }
435
      }
436
      
437
      return false;
438
   }
439
   
440
   /**
441
    * Removes all entries from all scopes in the stack, top scope first.
442
    * Does not remove any scopes.
443
    */
444
   public void clear()
445
   {
446
      ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
447
      while (li.hasPrevious())
448
      {
449
         li.previous().getDictionary(false).clear();
450
      }
451
   }
452
   
453
   /**
454
    * Searches all scopes from the top of the stack down for the specified
455
    * key and value pair and returns 0-based index of the depth where
456
    * that pair is found.  Both the key and the value must be matched to
457
    * the given parameters for this to succeed.
458
    *
459
    * @param   key
460
    *          The lookup key.
461
    * @param   value
462
    *          The value to search.
463
    * 
464
    * @return  0-based index of the scope depth at which value is found.
465
    */
466
   public int locate(K key, V value)
467
   {
468
      ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
469
      Map<K, V> dictionary = null;
470
      V o = null;
471
      
472
      key = processKey(key);
473
      int depth = 0;
474
      
475
      while (li.hasPrevious())
476
      {
477
         dictionary = li.previous().getDictionary(false);
478
         o = dictionary.get(key);
479
         
480
         if (o != null && o.equals(value))
481
         {
482
            return depth;
483
         }
484
         
485
         depth++;
486
      }
487
      
488
      return -1;
489
   }
490
   
491
   /**
492
    * Returns the dictionary from the scope specified, or <code>null</code> if
493
    * the specified scope does not exist.
494
    *
495
    * @param   scope
496
    *          The depth (in number of scopes from the top of the stack) at
497
    *          which the value must be set. Use -1 for the global scope.
498
    * @param   create
499
    *          <code>true</code> to create a dictionary for this scope if one
500
    *          does not already exist;  <code>false</code> to have an empty
501
    *          (immutable) dictionary returned if one does not already exist.
502
    * 
503
    * @return  The dictionary, if any, associated with the specified scope.
504
    *          Note that this may be an immutable, empty map if no dictionary
505
    *          exists at this scope, and <code>create</code> is set to
506
    *          <code>false</code>.  If the scope itself does not exist, then
507
    *          <code>null</code> is returned.
508
    */
509
   public Map<K, V> getDictionaryAtScope(int scope, boolean create)
510
   {
511
      Node<K, V> node = getNodeAt(scope);
512
      
513
      return (node != null) ? node.getDictionary(create) : null;
514
   }
515
   
516
   /**
517
    * Remove the entry with the specified key from all scopes up to and including the scope
518
    * specified.
519
    *
520
    * @param   key
521
    *          The lookup key.
522
    * @param   scope
523
    *          The depth (in number of scopes from the top of the stack) from which the entry
524
    *          is to be removed. It will be removed from every scope in which it exists, up to
525
    *          and including this depth.
526
    * 
527
    * @return  The number of scopes from which the entry was removed, or {@code 0} if it was not
528
    *          found.
529
    */
530
   public int removeEntryThroughScope(K key, int scope)
531
   {
532
      key = processKey(key);
533
      int count = 0;
534
      
535
      ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
536
      
537
      for (int depth = 0; li.hasPrevious() && depth <= scope; depth++)
538
      {
539
         Node<K, V> node = li.previous();
540
         
541
         if (node.getDictionary(false).remove(key) != null)
542
         {
543
            count++;
544
         }
545
      }
546
      
547
      return count;
548
   }
549
   
550
   /**
551
    * Remove the entry with the specified key from the scope specified.
552
    *
553
    * @param   key
554
    *          The lookup key.
555
    * @param   scope
556
    *          The depth (in number of scopes from the top of the stack) from
557
    *          which the entry is to be removed. 
558
    * 
559
    * @return  <code>true</code> if the value was removed set, or
560
    *          <code>false</code> if it was not found.
561
    */
562
   public boolean removeEntryAtScope(K key, int scope)
563
   {
564
      Map<K, V> dict = getDictionaryAtScope(scope, false);
565
      
566
      return (dict == null || dict.remove(processKey(key)) != null);
567
   }
568
   
569
   /**
570
    * Sets specified value within the entry in the scope specified.
571
    *
572
    * @param   key
573
    *          The lookup key.
574
    * @param   scope
575
    *          The depth (in number of scopes from the top of the stack) at
576
    *          which the value must be set. 
577
    * @param   value
578
    *          The replacement value.
579
    * 
580
    * @return  <code>true</code> if the value was successfully set, or
581
    *          <code>false</code> if any error occurred.
582
    */
583
   public boolean setValueAtScope(K key, int scope, V value)
584
   {
585
      Map<K, V> dict = getDictionaryAtScope(scope, true);
586
      
587
      if (dict != null)
588
      {
589
         dict.put(processKey(key), value);
590
         return true;
591
      }
592
      
593
      return false;
594
   }
595
   
596
   /**
597
    * Gets the value for the given key in the scope specified.
598
    *
599
    * @param   key
600
    *          The lookup key.
601
    * @param   scope
602
    *          The depth (in number of scopes from the top of the stack) from
603
    *          which the value must be gotten. Use -1 to reference the global
604
    *          scope.
605
    * 
606
    * @return  The object found or <code>null</code> if no such name exists.
607
    */
608
   public V getValueAtScope(K key, int scope)
609
   {
610
      Map<K, V> dict = getDictionaryAtScope(scope, false);
611
      
612
      return (dict == null) ? null : dict.get(processKey(key));
613
   }
614
   
615
   /**
616
    * Execute the given code for all values, in all scopes, for the specified key.
617
    * <p>
618
    * This will walk all dictionaries, from the innermost scope to the global scope.  If a 
619
    * dictionary mapping doesn't exist in a certain scope for this key, that scope will not be
620
    * processed.
621
    * <p>
622
    * The scope processing will continue until the caller (via the code function) decides to
623
    * terminate the processing.
624
    * 
625
    * @param    key
626
    *           The key which needs to have its values processed.
627
    * @param    code
628
    *           The code to be applied to each dictionary, for the given key.  If this function
629
    *           returns true, the search will end.
630
    */
631
   public void apply(K key, Function<Map<K, V>, Boolean> code)
632
   {
633
      // walk all dictionaries, from the innermost scope to the global scope
634
      ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
635
      while (li.hasPrevious())
636
      {
637
         Node<K, V> node = li.previous();
638
         
639
         Map<K, V> dictionary = node.getDictionary(false);
640
         
641
         if (!dictionary.containsKey(key))
642
         {
643
            continue;
644
         }
645
         
646
         if (code.apply(dictionary))
647
         {
648
            break;
649
         }
650
      }
651
   }
652
   
653
   /**
654
    * Searches all scopes from the top of the stack down for a given entry.
655
    *
656
    * @param   key
657
    *          The lookup key.
658
    *
659
    * @return  The user-defined object associated with entry or <code>null</code> if not found.
660
    */
661
   public V lookup(K key)
662
   {
663
      return lookup(key, scopes.size());
664
   }
665
   
666
   /**
667
    * Searches all scopes from the specified scope in the stack down for a given entry.
668
    *
669
    * @param   key
670
    *          The lookup key.
671
    * @param   fromScope
672
    *          The 1-based index from which to start the backwards walk of scopes to be searched.
673
    *          For instance, to search the full dictionary, the size of the dictionary should be
674
    *          passed for this parameter; to search only the bottom scope, 1 should be passed.
675
    *
676
    * @return  The user-defined object associated with entry or <code>null</code> if not found.
677
    */
678
   public V lookup(K key, int fromScope)
679
   {
680
      ListIterator<Node<K, V>> li = scopes.listIterator(fromScope);
681
      Map<K, V> dictionary = null;
682
      V o = null;
683
      
684
      key = processKey(key);
685
      
686
      while (li.hasPrevious())
687
      {
688
         dictionary = li.previous().getDictionary(false);
689
         o = dictionary.get(key);
690
         if (o != null)
691
         {
692
            return o;
693
         }
694
      }
695
      
696
      return null;
697
   }
698
   
699
   /**
700
    * Searches all scopes bottom up, starting from the global to the innermost scope.
701
    *
702
    * @param   key
703
    *          The lookup key.
704
    *
705
    * @return  The user-defined object associated with entry or <code>null</code> if not found.
706
    */
707
   public V reverseLookup(K key)
708
   {
709
      return reverseLookup(key, 0);
710
   }
711
   
712
   /**
713
    * Searches all scopes bottom up, starting from the specified scope to the innermost scope.
714
    *
715
    * @param   key
716
    *          The lookup key.
717
    * @param   scope
718
    *          The 0-based scope at which to start the reverse lookup. Must be in the range
719
    *          {@code (scope &gt;= 0 && scope &lt; size())}.
720
    *
721
    * @return  The user-defined object associated with entry or <code>null</code> if not found.
722
    * 
723
    * @throws  IndexOutOfBoundsException
724
    *          if {@code scope} is outside of the permitted range.
725
    */
726
   public V reverseLookup(K key, int scope)
727
   {
728
      Iterator<Node<K, V>> li = scopes.listIterator(scope);
729
      Map<K, V> dictionary = null;
730
      V o = null;
731
      
732
      key = processKey(key);
733
      
734
      while (li.hasNext())
735
      {
736
         dictionary = li.next().getDictionary(false);
737
         o = dictionary.get(key);
738
         if (o != null)
739
         {
740
            return o;
741
         }
742
      }
743
      
744
      return null;
745
   }
746
   
747
   /**
748
    * Searches all scopes from the top of the stack down for a given entry.
749
    *
750
    * @param   key
751
    *          The lookup key.
752
    * 
753
    * @return  The nesting level (depth) of the scope where the entry is
754
    *          defined first or -1 if not found.
755
    */
756
   public int locate(K key)
757
   {
758
      ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
759
      Map<K, V> dictionary = null;
760
      V o = null;
761
      
762
      key = processKey(key);
763
      
764
      int depth = 0;
765
      while (li.hasPrevious())
766
      {
767
         dictionary = li.previous().getDictionary(false);
768
         o = dictionary.get(key);
769
         if (o != null)
770
         {
771
            return depth;
772
         }
773
         depth ++;
774
      }
775
      
776
      return -1;
777
   }
778
   
779
   /**
780
    * Returns a set containing all defined keys.
781
    *
782
    * @return   The set view of all keys defined in all scopes,
783
    */
784
   public Set<K> keySet()
785
   {
786
      ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
787
      Map<K, V> dictionary = null;
788
      Set<K> s = new HashSet<>();
789
      
790
      while (li.hasPrevious())
791
      {
792
         dictionary = li.previous().getDictionary(false);
793
         s.addAll(dictionary.keySet());
794
      }
795
      
796
      return s;
797
   }
798
   
799
   /**
800
    * Returns a set containing all defined keys in the scope specified.
801
    *
802
    * @param   scope
803
    *          The depth (in number of scopes from the top of the stack) from
804
    *          which the keys are collected. Use -1 to reference the global
805
    *          scope.
806
    * 
807
    * @return  The set of all keys from the specified scope (and only that
808
    *          scope).
809
    */
810
   public Set<K> keySet(int scope)
811
   {
812
      Map<K, V> dictionary = null;
813
      Set<K> keys = new HashSet<>();
814
      
815
      if (scope == -1)
816
      {
817
         dictionary = scopes.getFirst().getDictionary(false);
818
         keys.addAll(dictionary.keySet());
819
      }
820
      else
821
      {
822
         ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
823
         
824
         int depth = 0;
825
         
826
         while (li.hasPrevious())
827
         {
828
            dictionary = li.previous().getDictionary(false);
829
            
830
            if (depth == scope)
831
            {
832
               keys.addAll(dictionary.keySet());
833
               break;
834
            }
835
            depth++;
836
         }
837
      }
838
      
839
      return keys;
840
   }
841
   
842
   /**
843
    * Returns a set containing all defined entries (as <code>Map.Entry</code>
844
    * objects).
845
    *
846
    * @return   The set view of all entries defined in all scopes,
847
    */
848
   public Set<Map.Entry<K, V>> entrySet()
849
   {
850
      ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
851
      Map<K, V> dictionary = null;
852
      Set<Map.Entry<K, V>> s = new HashSet<>();
853
      
854
      while (li.hasPrevious())
855
      {
856
         dictionary = li.previous().getDictionary(false);
857
         s.addAll(dictionary.entrySet());
858
      }
859
      
860
      return s;
861
   }
862
   
863
   /**
864
    * Returns a set containing all defined entries (as <code>Map.Entry</code>
865
    * objects) in the scope specified.
866
    *
867
    * @param   scope
868
    *          The depth (in number of scopes from the top of the stack) from
869
    *          which the entries are collected. Use -1 to reference the global
870
    *          scope.
871
    * 
872
    * @return  The set of all entries from the specified scope (and only that
873
    *          scope).
874
    */
875
   public Set<Map.Entry<K, V>> entrySet(int scope)
876
   {
877
      Map<K, V> dictionary = null;
878
      Set<Map.Entry<K, V>> entries = new HashSet<>();
879
      
880
      if (scope == -1)
881
      {
882
         dictionary = scopes.getFirst().getDictionary(false);
883
         entries.addAll(dictionary.entrySet());
884
      }
885
      else
886
      {
887
         ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
888
         
889
         int depth = 0;
890
         
891
         while (li.hasPrevious())
892
         {
893
            dictionary = li.previous().getDictionary(false);
894
            
895
            if (depth == scope)
896
            {
897
               entries.addAll(dictionary.entrySet());
898
               break;
899
            }
900
            depth++;
901
         }
902
      }
903
      
904
      return entries;
905
   }
906
   
907
   /**
908
    * Returns an unmodifiable collection view of all defined values.
909
    * 
910
    * @return   The collection view of all values defined in all scopes.
911
    */
912
   public Collection<V> values()
913
   {
914
      Collection<V> view = new AbstractCollection<V>()
915
      {
916
         // iterator of scopes from most current to least current
917
         private final ListIterator<Node<K, V>> scopeIter = scopes.listIterator(scopes.size());
918
         
919
         // total number of elements across all scopes
920
         private int size = -1;
921
         
922
         // this object iterates over every value in every dictionary in every scope
923
         @Override
924
         public Iterator<V> iterator()
925
         {
926
            Iterator<V> iter = new Iterator<V>()
927
            {
928
               // iterator over the current scope's dictionary
929
               private Iterator<V> dictIter = null;
930
               
931
               // next value
932
               private V next = null;
933
               
934
               // can another value be iterated?
935
               public boolean hasNext()
936
               {
937
                  if (next == null)
938
                  {
939
                     advance();
940
                  }
941
                  
942
                  return (next != null);
943
               }
944
               
945
               // get the next value, if any
946
               public V next()
947
               {
948
                  if (!hasNext())
949
                  {
950
                     throw new NoSuchElementException();
951
                  }
952
                  
953
                  V rv = next;
954
                  next = null;
955
                  
956
                  return rv;
957
               }
958
               
959
               // no-op
960
               public void remove()
961
               {
962
                  throw new UnsupportedOperationException();
963
               }
964
               
965
               // iterate forward one element; if current node is exhausted, move to previous
966
               // scope and try again
967
               private void advance()
968
               {
969
                  while (next == null)
970
                  {
971
                     if (dictIter != null && dictIter.hasNext())
972
                     {
973
                        next = dictIter.next();
974
                        
975
                        // found one
976
                        return;
977
                     }
978
                     
979
                     if (scopeIter.hasPrevious())
980
                     {
981
                        Node<K, V> node = scopeIter.previous();
982
                        dictIter = node.getDictionary(false).values().iterator();
983
                     }
984
                     else
985
                     {
986
                        dictIter = null;
987
                        
988
                        // nothing left to iterate
989
                        return;
990
                     }
991
                  }
992
               }
993
            };
994
            
995
            return iter;
996
         }
997
         
998
         // returns number of all values across all scopes
999
         @Override
1000
         public int size()
1001
         {
1002
            if (size < 0)
1003
            {
1004
               int count = 0;
1005
               
1006
               for (Node<K, V> node : scopes)
1007
               {
1008
                  count += node.dictionary.size();
1009
               }
1010
               
1011
               size = count;
1012
            }
1013
            
1014
            return size;
1015
         }
1016
      };
1017
      
1018
      return view;
1019
   }
1020
   
1021
   /**
1022
    * Returns a collection containing all defined values in the scope
1023
    * specified.
1024
    *
1025
    * @param   scope
1026
    *          The depth (in number of scopes from the top of the stack) from
1027
    *          which the values are collected. Use -1 to reference the global
1028
    *          scope.
1029
    * 
1030
    * @return  The set of all values from the specified scope (and only that
1031
    *          scope).
1032
    */
1033
   public Collection<V> values(int scope)
1034
   {
1035
      Map<K, V> dictionary = null;
1036
      Collection<V> values = null;
1037
      
1038
      if (scope == -1)
1039
      {
1040
         dictionary = scopes.getFirst().getDictionary(false);
1041
         values = dictionary.values();
1042
      }
1043
      else
1044
      {
1045
         ListIterator<Node<K, V>> li = scopes.listIterator(scopes.size());
1046
         
1047
         int depth = 0;
1048
         
1049
         while (li.hasPrevious())
1050
         {
1051
            dictionary = li.previous().getDictionary(false);
1052
            
1053
            if (depth == scope)
1054
            {
1055
               values = dictionary.values();
1056
               break;
1057
            }
1058
            depth++;
1059
         }
1060
      }
1061
      
1062
      if (values == null)
1063
      {
1064
         return Collections.emptyList();
1065
      }
1066
      
1067
      return Collections.unmodifiableCollection(values);
1068
   }
1069
   
1070
   /**
1071
    * Prints a report of all entries found by scope, starting at the bottom
1072
    * of the stack (of scopes) working toward the top.
1073
    *
1074
    * @param   output
1075
    *          <code>PrintStream</code> object to which to dump.
1076
    */
1077
   public void dump(PrintStream output)
1078
   {
1079
      dump(new PrintWriter(output, true));
1080
   }
1081
   
1082
   /**
1083
    * Prints a report of all entries found by scope, starting at the bottom
1084
    * of the stack (of scopes) working toward the top.
1085
    *
1086
    * @param   output
1087
    *          <code>PrintWriter</code> object to which to dump.
1088
    */
1089
   public void dump(PrintWriter output)
1090
   {
1091
      ListIterator<Node<K, V>> li = scopes.listIterator(0);
1092
      Node<K, V> n = null;
1093
      
1094
      int depth = 0;
1095
      while (li.hasNext())
1096
      {
1097
         n = li.next();
1098
         dumpScope(n, depth++, "", output);
1099
      }
1100
      
1101
      output.println("=================================");
1102
   }
1103
   
1104
   /**
1105
    * Prints a report of all entries found in the current scope.
1106
    *
1107
    * @param   text
1108
    *          Additional text to print.
1109
    * @param   output
1110
    *          <code>PrintStream</code> object to which to dump.
1111
    */
1112
   public void dumpCurrentScope(String text, PrintStream output)
1113
   {
1114
      dumpCurrentScope(text, new PrintWriter(output, true));
1115
   }
1116
   
1117
   /**
1118
    * Prints a report of all entries found in the current scope.
1119
    *
1120
    * @param   text
1121
    *          Additional text to print.
1122
    * @param   output
1123
    *          <code>PrintWriter</code> object to which to dump.
1124
    */
1125
   public void dumpCurrentScope(String text, PrintWriter output)
1126
   {
1127
      Node<K, V> n = scopes.getLast();
1128
      if (n != null)
1129
      {
1130
         dumpScope(n, scopes.size(), text, output);
1131
      }
1132
   }
1133
   
1134
   /**
1135
    * Prints a report of all entries found by scope, starting at the bottom
1136
    * of the stack (of scopes) working toward the top. Uses the
1137
    * <code>System.err</code> stream for output.
1138
    */
1139
   public void dump()
1140
   {
1141
      dump(System.err);
1142
   }
1143
   
1144
   /**
1145
    * Subclasses which need to process the dictionary lookup key before it is
1146
    * used must override this method.  This default implementation simply
1147
    * returns the <code>key</code> argument unchanged.
1148
    *
1149
    * @param   key
1150
    *          The lookup key.
1151
    *
1152
    * @return  The <code>key</code> argument.
1153
    */
1154
   public K processKey(K key)
1155
   {
1156
      return key;
1157
   }
1158
   
1159
   /**
1160
    * Obtain the node at the specified depth from the top of the stack.
1161
    *
1162
    * @param   scope
1163
    *          The depth (in number of scopes from the top of the stack) from
1164
    *          which the value must be gotten. Use -1 to reference the global
1165
    *          scope.
1166
    *
1167
    * @return  Node associated with the specified scope.
1168
    */
1169
   protected Node<K, V> getNodeAt(int scope)
1170
   {
1171
      Node<K, V> node = null;
1172
      
1173
      if (scope == -1)
1174
      {
1175
         node = scopes.getFirst();
1176
      }
1177
      else if (scope == 0)
1178
      {
1179
         node = scopes.getLast();
1180
      }
1181
      else
1182
      {
1183
         int sz  = scopes.size();
1184
         int idx = sz - 1 - scope;
1185
         
1186
         if (idx >= 0 && idx < sz)
1187
         {
1188
            node = scopes.get(idx);
1189
         }
1190
      }
1191
      
1192
      return node;
1193
   }
1194
   
1195
   /**
1196
    * Prints a report of all entries found in a given scope.
1197
    * 
1198
    * @param   n
1199
    *          The <code>Node</code> instance to dump.
1200
    * @param   depth
1201
    *          The indent level.
1202
    * @param   text
1203
    *          Additional text to print.
1204
    * @param   output
1205
    *          <code>PrintWriter</code> object to which to dump.
1206
    */
1207
   private void dumpScope(Node<K,V> n, int depth, String text, PrintWriter output)
1208
   {
1209
      Iterator<Map.Entry<K, V>> si = null;
1210
      StringBuilder indent = new StringBuilder();
1211
      
1212
      for (int i = 0; i < depth - 1; i ++)
1213
      {
1214
         indent.append("--- ");
1215
      }
1216
      
1217
      output.println(indent + text + " scope " + (depth + 1) + " --- " + n.getExtra());
1218
      si = n.getDictionary(false).entrySet().iterator();
1219
      
1220
      indent.append("    ");
1221
      while (si.hasNext())
1222
      {
1223
         output.println(indent.toString() + si.next());
1224
      }
1225
   }
1226
   
1227
   /**
1228
    * Represents complex items on the stack of scopes.
1229
    * Every item is made of a dictionary and a user-specific object.
1230
    */
1231
   protected static class Node<K, V>
1232
   implements Cloneable
1233
   {
1234
      /** Represents an instance of dictionary. */
1235
      private Map<K, V> dictionary = null;
1236
      
1237
      /** Stores a user supplied object associated with the dictionary. */
1238
      private Object extra = null;
1239
      
1240
      /**
1241
       * The only constructor.
1242
       *
1243
       * @param   extra
1244
       *          Any object that will be associated with this node.
1245
       */
1246
      public Node(Object extra)
1247
      {
1248
         this.extra = extra;
1249
      }
1250
      
1251
      /**
1252
       * Clone this instance.
1253
       * 
1254
       * @return   A copy of this instance.
1255
       */
1256
      @Override
1257
      protected Node<K, V> clone()
1258
      {
1259
         Node<K, V> node = new Node<>(this.extra);
1260
         node.dictionary = null;
1261
         
1262
         if (dictionary != null)
1263
         {
1264
            node.dictionary = new HashMap<>();
1265
            node.dictionary.putAll(this.dictionary);
1266
         }
1267
         
1268
         return node;
1269
      }
1270
      
1271
      /**
1272
       * Accesses the dictionary at this node.  If no dictionary is associated
1273
       * with this node, one can be created optionally.  Otherwise, an empty,
1274
       * immutable map is returned.
1275
       * 
1276
       * @param   create
1277
       *          <code>true</code> to create a dictionary for this node if
1278
       *          one does not already exist;  <code>false</code> to use have
1279
       *          an empty (immutable) dictionary returned if one does not
1280
       *          already exist.
1281
       *
1282
       * @return  Map object associated with this node, or an empty map if no
1283
       *          such association exists.
1284
       */
1285
      public Map<K, V> getDictionary(boolean create)
1286
      {
1287
         if (dictionary == null)
1288
         {
1289
            if (create)
1290
            {
1291
               dictionary = new HashMap<>(4);
1292
            }
1293
            else
1294
            {
1295
               return Collections.emptyMap();
1296
            }
1297
         }
1298
         
1299
         return dictionary;
1300
      }
1301
      
1302
      /**
1303
       * Accesses the user supplied object at this node.
1304
       *
1305
       * @return  Object associated with this node.
1306
       */
1307
      public Object getExtra()
1308
      {
1309
         return extra;
1310
      }
1311
      
1312
      /**
1313
       * Replaces the user supplied object at this node.
1314
       *
1315
       * @param   extra
1316
       *          Any object that will be associated with this node.
1317
       */
1318
      public void setExtra(Object extra)
1319
      {
1320
         this.extra = extra;
1321
      }
1322
   }
1323
}