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