View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal;
18  
19  import java.util.BitSet;
20  
21  /**
22   * The set of symbols declared in a lexical scope.
23   * <p>The functional scope determines the symbol identifiers.</p>
24   * <p>We use 2 bits per symbol s; bit (s*2)+0 sets the actual symbol as lexical (let), bit (s*2)+1 as a const.
25   * There are actually only 2 used states: 1 and 3</p>
26   */
27  public class LexicalScope {
28  
29      /**
30       * Number of bits in a long.
31       */
32      protected static final int BITS_PER_LONG = 64;
33  
34      /**
35       * Bits per symbol.
36       * let (b + 0) + const (b + 1).
37       */
38      protected static final int BITS_PER_SYMBOL = 2;
39  
40      /**
41       * From a symbol number to a starting symbol bit number.
42       */
43      protected static final int SYMBOL_SHIFT = BITS_PER_SYMBOL - 1;
44  
45      /**
46       * Bitmask for symbols.
47       */
48      protected static final long SYMBOL_MASK = (1L << BITS_PER_SYMBOL - 1) - 1; // 3, as 1+2, 2 bits
49  
50      /**
51       * Number of symbols.
52       */
53      protected int count;
54  
55      /**
56       * The mask of symbols in the scope.
57       */
58      protected long symbols;
59  
60      /**
61       * Symbols after bit 64 (aka symbol 32 when 2 bits per symbol).
62       */
63      protected BitSet moreSymbols;
64  
65      /**
66       * Create a scope.
67       */
68      public LexicalScope() {
69      }
70  
71      /**
72       * Frame copy ctor base.
73       */
74      protected LexicalScope(final LexicalScope other) {
75          this.symbols = other.symbols;
76          final BitSet otherMoreSymbols = other.moreSymbols;
77          this.moreSymbols = otherMoreSymbols != null ? (BitSet) otherMoreSymbols.clone() : null;
78          this.count = other.count;
79      }
80  
81      /**
82       * Adds a constant in this scope.
83       *
84       * @param symbol the symbol
85       * @return true if registered, false if symbol was already registered
86       */
87      public boolean addConstant(final int symbol) {
88          final int letb = symbol << SYMBOL_SHIFT ;
89          if (!isSet(letb)) {
90              throw new IllegalStateException("const not declared as symbol " + symbol);
91          }
92          final int bit = symbol << SYMBOL_SHIFT | 1;
93          return set(bit);
94      }
95  
96      /**
97       * Adds a symbol in this scope.
98       *
99       * @param symbol the symbol
100      * @return true if registered, false if symbol was already registered
101      */
102     public boolean addSymbol(final int symbol) {
103         final int bit = symbol << SYMBOL_SHIFT ;
104         if (set(bit)) {
105             count += 1;
106             return true;
107         }
108         return false;
109     }
110 
111     /**
112      * Clear all symbols.
113      *
114      * @param cleanSymbol a (optional, may be null) functor to call for each cleaned symbol
115      */
116     public final void clearSymbols(final java.util.function.IntConsumer cleanSymbol) {
117         // undefine symbols getting out of scope
118         if (cleanSymbol != null) {
119             long clean = symbols;
120             while (clean != 0L) {
121                 final int bit = Long.numberOfTrailingZeros(clean);
122                 final int s = bit >> SYMBOL_SHIFT;
123                 cleanSymbol.accept(s);
124                 // call clean for symbol definition (3 as a mask for 2 bits,1+2)
125                 clean &= ~(SYMBOL_MASK << bit);
126             }
127             // step by bits per symbol
128             int bit = moreSymbols != null ? moreSymbols.nextSetBit(0) : -1;
129             while (bit >= 0) {
130                 final int s = bit + BITS_PER_LONG >> SYMBOL_SHIFT;
131                 cleanSymbol.accept(s);
132                 bit = moreSymbols.nextSetBit(bit + BITS_PER_SYMBOL);
133             }
134         }
135         // internal cleansing
136         symbols = 0L;
137         count = 0;
138         if (moreSymbols != null) {
139             moreSymbols.clear();
140         }
141     }
142 
143     /**
144      * @return the number of symbols defined in this scope.
145      */
146     public int getSymbolCount() {
147         return count;
148     }
149 
150     /**
151      * Checks whether a symbol has already been declared.
152      *
153      * @param symbol the symbol
154      * @return true if declared, false otherwise
155      */
156     public boolean hasSymbol(final int symbol) {
157         final int bit = symbol << SYMBOL_SHIFT;
158         return isSet(bit);
159     }
160 
161     /**
162      * Checks whether a symbol is declared as a constant.
163      *
164      * @param symbol the symbol
165      * @return true if declared as constant, false otherwise
166      */
167     public boolean isConstant(final int symbol) {
168         final int bit = symbol << SYMBOL_SHIFT | 1;
169         return isSet(bit);
170     }
171 
172     /**
173      * Tests whether a given bit (not symbol) is set.
174      *
175      * @param bit the bit
176      * @return true if set
177      */
178     private boolean isSet(final int bit) {
179         if (bit < BITS_PER_LONG) {
180             return (symbols & 1L << bit) != 0L;
181         }
182         return moreSymbols != null && moreSymbols.get(bit - BITS_PER_LONG);
183     }
184 
185     /**
186      * Ensures more symbols can be stored.
187      *
188      * @return the set of more symbols
189      */
190     private BitSet moreBits() {
191         if (moreSymbols == null) {
192             moreSymbols = new BitSet();
193         }
194         return moreSymbols;
195     }
196 
197     /**
198      * Sets a given bit (not symbol).
199      *
200      * @param bit the bit
201      * @return true if it was actually set, false if it was set before
202      */
203     private boolean set(final int bit) {
204         if (bit < BITS_PER_LONG) {
205             if ((symbols & 1L << bit) != 0L) {
206                 return false;
207             }
208             symbols |= 1L << bit;
209         } else {
210             final int bit64 = bit - BITS_PER_LONG;
211             final BitSet ms = moreBits();
212             if (ms.get(bit64)) {
213                 return false;
214             }
215             ms.set(bit64, true);
216         }
217         return true;
218     }
219 }