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 org.apache.commons.jexl3.JexlContext;
20  import org.apache.commons.jexl3.JexlOptions;
21  import org.apache.commons.jexl3.parser.ASTJexlLambda;
22  
23  import java.util.Arrays;
24  import java.util.Objects;
25  
26  /**
27   * A Script closure.
28   */
29  public class Closure extends Script {
30      /** The frame. */
31      protected final Frame frame;
32      /** The options. */
33      protected final JexlOptions options;
34  
35      /**
36       * Creates a closure.
37       * @param theCaller the calling interpreter
38       * @param lambda the lambda
39       */
40      protected Closure(final Interpreter theCaller, final ASTJexlLambda lambda) {
41          super(theCaller.jexl, null, lambda);
42          frame = lambda.createFrame(theCaller.frame);
43          final JexlOptions callerOptions = theCaller.options;
44          options = callerOptions != null ? callerOptions.copy() :  null;
45      }
46  
47      /**
48       * Creates a curried version of a script.
49       * @param base the base script
50       * @param args the script arguments
51       */
52      protected Closure(final Script base, final Object[] args) {
53          super(base.jexl, base.source, base.script);
54          final Frame sf = (base instanceof Closure) ? ((Closure) base).frame :  null;
55          frame = sf == null
56                  ? script.createFrame(args)
57                  : sf.assign(args);
58          JexlOptions closureOptions = null;
59          if (base instanceof Closure) {
60              closureOptions = ((Closure) base).options;
61          }
62          options = closureOptions != null ? closureOptions.copy() :  null;
63      }
64  
65      @Override
66      public int hashCode() {
67          // CSOFF: Magic number
68          int hash = 17;
69          hash = 31 * hash + Objects.hashCode(jexl);
70          hash = 31 * hash + Objects.hashCode(source);
71          hash = 31 * hash + (frame != null ? Arrays.deepHashCode(frame.nocycleStack(this)) : 0);
72          // CSON: Magic number
73          return hash;
74      }
75  
76      @Override
77      public boolean equals(final Object obj) {
78          if (obj == null) {
79              return false;
80          }
81          if (getClass() != obj.getClass()) {
82              return false;
83          }
84          final Closure other = (Closure) obj;
85          if (this.jexl != other.jexl) {
86              return false;
87          }
88          if (!Objects.equals(this.source, other.source)) {
89              return false;
90          }
91          if (this.frame == other.frame) {
92              return true;
93          }
94          return Arrays.deepEquals(
95                  this.frame.nocycleStack(this),
96                  other.frame.nocycleStack(other));
97      }
98  
99      @Override
100     public String[] getUnboundParameters() {
101         return frame.getUnboundParameters();
102     }
103 
104     /**
105      * Sets the captured index of a given symbol, ie the target index of a parent
106      * captured symbol in this closure's frame.
107      * <p>This is meant to allow a locally defined function to "see" and call
108      * itself as a local (captured) variable;
109      * in other words, this allows recursive call of a function.
110      * @param symbol the symbol index (in the caller of this closure)
111      * @param value the value to set in the local frame
112      */
113     public void setCaptured(final int symbol, final Object value) {
114         if (script instanceof ASTJexlLambda) {
115             final ASTJexlLambda lambda = (ASTJexlLambda) script;
116             final Scope scope = lambda.getScope();
117             if (scope != null) {
118                 final Integer reg = scope.getCaptured(symbol);
119                 if (reg != null) {
120                     frame.set(reg, value);
121                 }
122             }
123         }
124     }
125 
126     @Override
127     public Object evaluate(final JexlContext context) {
128         return execute(context, (Object[])null);
129     }
130 
131     @Override
132     public Object execute(final JexlContext context) {
133         return execute(context, (Object[])null);
134     }
135 
136     @Override
137     public Object execute(final JexlContext context, final Object... args) {
138         final Frame local = frame != null? frame.assign(args) : null;
139         final Interpreter interpreter = createInterpreter(context, local, options);
140         return interpreter.runClosure(this, null);
141     }
142 
143     @Override
144     public Callable callable(final JexlContext context, final Object... args) {
145         final Frame local = frame != null? frame.assign(args) : null;
146         return new Callable(createInterpreter(context, local, options)) {
147             @Override
148             public Object interpret() {
149                 return interpreter.runClosure(Closure.this, null);
150             }
151         };
152     }
153 }