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