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    *      http://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 symbol identifiers are determined by the functional scope.</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       * Number of bits in a long.
30       */
31      protected static final int BITS_PER_LONG = 64;
32      /**
33       * Bits per symbol.
34       * let (b + 0) + const (b + 1).
35       */
36      protected static final int BITS_PER_SYMBOL = 2;
37      /**
38       * From a symbol number to a starting symbol bit number.
39       */
40      protected static final int SYMBOL_SHIFT = BITS_PER_SYMBOL - 1;
41      /**
42       * Bitmask for symbols.
43       */
44      protected static final long SYMBOL_MASK = (1L << BITS_PER_SYMBOL - 1) - 1; // 3, as 1+2, 2 bits
45      /**
46       * Number of symbols.
47       */
48      protected int count;
49      /**
50       * The mask of symbols in the scope.
51       */
52      protected long symbols;
53      /**
54       * Symbols after bit 64 (aka symbol 32 when 2 bits per symbol).
55       */
56      protected BitSet moreSymbols;
57  
58      /**
59       * Create a scope.
60       */
61      public LexicalScope() {
62      }
63  
64      /**
65       * Frame copy ctor base.
66       */
67      protected LexicalScope(final LexicalScope other) {
68          this.symbols = other.symbols;
69          final BitSet otherMoreSymbols = other.moreSymbols;
70          this.moreSymbols = otherMoreSymbols != null ? (BitSet) otherMoreSymbols.clone() : null;
71          this.count = other.count;
72      }
73  
74      /**
75       * Adds a constant in this scope.
76       *
77       * @param symbol the symbol
78       * @return true if registered, false if symbol was already registered
79       */
80      public boolean addConstant(final int symbol) {
81          final int letb = symbol << SYMBOL_SHIFT ;
82          if (!isSet(letb)) {
83              throw new IllegalStateException("const not declared as symbol " + symbol);
84          }
85          final int bit = symbol << SYMBOL_SHIFT | 1;
86          return set(bit);
87      }
88  
89      /**
90       * Adds a symbol in this scope.
91       *
92       * @param symbol the symbol
93       * @return true if registered, false if symbol was already registered
94       */
95      public boolean addSymbol(final int symbol) {
96          final int bit = symbol << SYMBOL_SHIFT ;
97          if (set(bit)) {
98              count += 1;
99              return true;
100         }
101         return false;
102     }
103 
104     /**
105      * Clear all symbols.
106      *
107      * @param cleanSymbol a (optional, may be null) functor to call for each cleaned symbol
108      */
109     public final void clearSymbols(final java.util.function.IntConsumer cleanSymbol) {
110         // undefine symbols getting out of scope
111         if (cleanSymbol != null) {
112             long clean = symbols;
113             while (clean != 0L) {
114                 final int bit = Long.numberOfTrailingZeros(clean);
115                 final int s = bit >> SYMBOL_SHIFT;
116                 cleanSymbol.accept(s);
117                 // call clean for symbol definition (3 as a mask for 2 bits,1+2)
118                 clean &= ~(SYMBOL_MASK << bit);
119             }
120             // step by bits per symbol
121             int bit = moreSymbols != null ? moreSymbols.nextSetBit(0) : -1;
122             while (bit >= 0) {
123                 final int s = bit + BITS_PER_LONG >> SYMBOL_SHIFT;
124                 cleanSymbol.accept(s);
125                 bit = moreSymbols.nextSetBit(bit + BITS_PER_SYMBOL);
126             }
127         }
128         // internal cleansing
129         symbols = 0L;
130         count = 0;
131         if (moreSymbols != null) {
132             moreSymbols.clear();
133         }
134     }
135 
136     /**
137      * @return the number of symbols defined in this scope.
138      */
139     public int getSymbolCount() {
140         return count;
141     }
142 
143     /**
144      * Checks whether a symbol has already been declared.
145      *
146      * @param symbol the symbol
147      * @return true if declared, false otherwise
148      */
149     public boolean hasSymbol(final int symbol) {
150         final int bit = symbol << SYMBOL_SHIFT;
151         return isSet(bit);
152     }
153 
154     /**
155      * Checks whether a symbol is declared as a constant.
156      *
157      * @param symbol the symbol
158      * @return true if declared as constant, false otherwise
159      */
160     public boolean isConstant(final int symbol) {
161         final int bit = symbol << SYMBOL_SHIFT | 1;
162         return isSet(bit);
163     }
164 
165     /**
166      * Whether a given bit (not symbol) is set.
167      * @param bit the bit
168      * @return true if set
169      */
170     private boolean isSet(final int bit) {
171         if (bit < BITS_PER_LONG) {
172             return (symbols & 1L << bit) != 0L;
173         }
174         return moreSymbols != null && moreSymbols.get(bit - BITS_PER_LONG);
175     }
176 
177     /**
178      * Ensures more symbols can be stored.
179      *
180      * @return the set of more symbols
181      */
182     private BitSet moreBits() {
183         if (moreSymbols == null) {
184             moreSymbols = new BitSet();
185         }
186         return moreSymbols;
187     }
188 
189     /**
190      * Sets a given bit (not symbol).
191      * @param bit the bit
192      * @return true if it was actually set, false if it was set before
193      */
194     private boolean set(final int bit) {
195         if (bit < BITS_PER_LONG) {
196             if ((symbols & 1L << bit) != 0L) {
197                 return false;
198             }
199             symbols |= 1L << bit;
200         } else {
201             final int bit64 = bit - BITS_PER_LONG;
202             final BitSet ms = moreBits();
203             if (ms.get(bit64)) {
204                 return false;
205             }
206             ms.set(bit64, true);
207         }
208         return true;
209     }
210 }