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.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      /** 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 Callable callable(final JexlContext context, final Object... args) {
67          final Frame local = frame != null ? frame.assign(args) : null;
68          return new Callable(createInterpreter(context, local, options)) {
69              @Override
70              public Object interpret() {
71                  return interpreter.runClosure(Closure.this);
72              }
73          };
74      }
75  
76      /**
77       * Enable lambda recursion.
78       * <p>Assign this lambda in its own frame if the symbol it is assigned to in its definition scope
79       * is captured in its body.</p>
80       * <p>This done allow a locally defined function to "see" and call  itself as a local (captured) variable.</p>
81       * Typical case is: <code>const f = (x)->x <= 0? 1 : x*f(x-1)</code>. Since assignment of f occurs after
82       * the lambda creation, we need to patch the lambda frame to expose itself through the captured symbol.
83       * @param parentFrame the parent calling frame
84       * @param symbol the symbol index (in the caller of this closure)
85       */
86      void captureSelfIfRecursive(final Frame parentFrame, final int symbol) {
87          if (script instanceof ASTJexlLambda) {
88              final Scope parentScope = parentFrame != null ? parentFrame.getScope() : null;
89              final Scope localScope = frame != null ? frame.getScope() : null;
90              if (parentScope != null  && localScope != null && parentScope == localScope.getParent()) {
91                  final Integer reg = localScope.getCaptured(symbol);
92                  if (reg != null) {
93                      frame.set(reg, this);
94                  }
95              }
96          }
97      }
98  
99      @Override
100     public boolean equals(final Object obj) {
101         if (obj == null) {
102             return false;
103         }
104         if (getClass() != obj.getClass()) {
105             return false;
106         }
107         final Closure other = (Closure) obj;
108         if (this.jexl != other.jexl) {
109             return false;
110         }
111         if (!Objects.equals(this.source, other.source)) {
112             return false;
113         }
114         if (this.frame == other.frame) {
115             return true;
116         }
117         return Arrays.deepEquals(
118                 this.frame.nocycleStack(this),
119                 other.frame.nocycleStack(other));
120     }
121 
122     @Override
123     public Object evaluate(final JexlContext context) {
124         return execute(context, (Object[])null);
125     }
126 
127     @Override
128     public Object execute(final JexlContext context) {
129         return execute(context, (Object[])null);
130     }
131 
132     @Override
133     public Object execute(final JexlContext context, final Object... args) {
134         final Frame local = frame != null ? frame.assign(args) : null;
135         final Interpreter interpreter = createInterpreter(context, local, options);
136         return interpreter.runClosure(this);
137     }
138 
139     @Override
140     public String[] getUnboundParameters() {
141         return frame.getUnboundParameters();
142     }
143 
144     @Override
145     public int hashCode() {
146         // CSOFF: Magic number
147         int hash = 17;
148         hash = 31 * hash + Objects.hashCode(jexl);
149         hash = 31 * hash + Objects.hashCode(source);
150         hash = 31 * hash + (frame != null ? Arrays.deepHashCode(frame.nocycleStack(this)) : 0);
151         // CSON: Magic number
152         return hash;
153     }
154 
155 }