• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
today-context/src/main/java/infra/scripting/bsh/BshScriptUtils.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.scripting.bsh;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.lang.reflect.InvocationHandler;
23
import java.lang.reflect.Method;
24
import java.lang.reflect.Proxy;
25

26
import bsh.EvalError;
27
import bsh.Interpreter;
28
import bsh.Primitive;
29
import bsh.XThis;
30
import infra.core.NestedRuntimeException;
31
import infra.lang.Assert;
32
import infra.util.ClassUtils;
33
import infra.util.ObjectUtils;
34
import infra.util.ReflectionUtils;
35

36
/**
37
 * Utility methods for handling BeanShell-scripted objects.
38
 *
39
 * @author Rob Harrop
40
 * @author Juergen Hoeller
41
 * @author <a href="https://github.com/TAKETODAY">海子 Yang</a>
42
 * @since 4.0
43
 */
44
public abstract class BshScriptUtils {
×
45

46
  /**
47
   * Create a new BeanShell-scripted object from the given script source.
48
   * <p>With this {@code createBshObject} variant, the script needs to
49
   * declare a full class or return an actual instance of the scripted object.
50
   *
51
   * @param scriptSource the script source text
52
   * @return the scripted Java object
53
   * @throws EvalError in case of BeanShell parsing failure
54
   */
55
  public static Object createBshObject(String scriptSource) throws EvalError {
56
    return createBshObject(scriptSource, null, null);
×
57
  }
58

59
  /**
60
   * Create a new BeanShell-scripted object from the given script source,
61
   * using the default ClassLoader.
62
   * <p>The script may either be a simple script that needs a corresponding proxy
63
   * generated (implementing the specified interfaces), or declare a full class
64
   * or return an actual instance of the scripted object (in which case the
65
   * specified interfaces, if any, need to be implemented by that class/instance).
66
   *
67
   * @param scriptSource the script source text
68
   * @param scriptInterfaces the interfaces that the scripted Java object is
69
   * supposed to implement (may be {@code null} or empty if the script itself
70
   * declares a full class or returns an actual instance of the scripted object)
71
   * @return the scripted Java object
72
   * @throws EvalError in case of BeanShell parsing failure
73
   * @see #createBshObject(String, Class[], ClassLoader)
74
   */
75
  public static Object createBshObject(String scriptSource, Class<?> @Nullable ... scriptInterfaces) throws EvalError {
76
    return createBshObject(scriptSource, scriptInterfaces, ClassUtils.getDefaultClassLoader());
×
77
  }
78

79
  /**
80
   * Create a new BeanShell-scripted object from the given script source.
81
   * <p>The script may either be a simple script that needs a corresponding proxy
82
   * generated (implementing the specified interfaces), or declare a full class
83
   * or return an actual instance of the scripted object (in which case the
84
   * specified interfaces, if any, need to be implemented by that class/instance).
85
   *
86
   * @param scriptSource the script source text
87
   * @param scriptInterfaces the interfaces that the scripted Java object is
88
   * supposed to implement (may be {@code null} or empty if the script itself
89
   * declares a full class or returns an actual instance of the scripted object)
90
   * @param classLoader the ClassLoader to use for evaluating the script
91
   * @return the scripted Java object
92
   * @throws EvalError in case of BeanShell parsing failure
93
   */
94
  public static Object createBshObject(String scriptSource, Class<?> @Nullable [] scriptInterfaces, @Nullable ClassLoader classLoader)
95
          throws EvalError {
96

97
    Object result = evaluateBshScript(scriptSource, scriptInterfaces, classLoader);
×
98
    if (result instanceof Class<?> clazz) {
×
99
      try {
100
        return ReflectionUtils.accessibleConstructor(clazz).newInstance();
×
101
      }
102
      catch (Throwable ex) {
×
103
        throw new IllegalStateException("Could not instantiate script class: " + clazz.getName(), ex);
×
104
      }
105
    }
106
    else {
107
      return result;
×
108
    }
109
  }
110

111
  /**
112
   * Evaluate the specified BeanShell script based on the given script source,
113
   * returning the Class defined by the script.
114
   * <p>The script may either declare a full class or return an actual instance of
115
   * the scripted object (in which case the Class of the object will be returned).
116
   * In any other case, the returned Class will be {@code null}.
117
   *
118
   * @param scriptSource the script source text
119
   * @param classLoader the ClassLoader to use for evaluating the script
120
   * @return the scripted Java class, or {@code null} if none could be determined
121
   * @throws EvalError in case of BeanShell parsing failure
122
   */
123
  @Nullable
124
  static Class<?> determineBshObjectType(String scriptSource, @Nullable ClassLoader classLoader) throws EvalError {
125
    Assert.hasText(scriptSource, "Script source must not be empty");
×
126
    Interpreter interpreter = new Interpreter();
×
127
    if (classLoader != null) {
×
128
      interpreter.setClassLoader(classLoader);
×
129
    }
130
    Object result = interpreter.eval(scriptSource);
×
131
    if (result instanceof Class) {
×
132
      return (Class<?>) result;
×
133
    }
134
    else if (result != null) {
×
135
      return result.getClass();
×
136
    }
137
    else {
138
      return null;
×
139
    }
140
  }
141

142
  /**
143
   * Evaluate the specified BeanShell script based on the given script source,
144
   * keeping a returned script Class or script Object as-is.
145
   * <p>The script may either be a simple script that needs a corresponding proxy
146
   * generated (implementing the specified interfaces), or declare a full class
147
   * or return an actual instance of the scripted object (in which case the
148
   * specified interfaces, if any, need to be implemented by that class/instance).
149
   *
150
   * @param scriptSource the script source text
151
   * @param scriptInterfaces the interfaces that the scripted Java object is
152
   * supposed to implement (may be {@code null} or empty if the script itself
153
   * declares a full class or returns an actual instance of the scripted object)
154
   * @param classLoader the ClassLoader to use for evaluating the script
155
   * @return the scripted Java class or Java object
156
   * @throws EvalError in case of BeanShell parsing failure
157
   */
158
  static Object evaluateBshScript(String scriptSource, Class<?> @Nullable [] scriptInterfaces, @Nullable ClassLoader classLoader) throws EvalError {
159
    Assert.hasText(scriptSource, "Script source must not be empty");
×
160
    Interpreter interpreter = new Interpreter();
×
161
    interpreter.setClassLoader(classLoader);
×
162
    Object result = interpreter.eval(scriptSource);
×
163
    if (result != null) {
×
164
      return result;
×
165
    }
166
    else {
167
      // Simple BeanShell script: Let's create a proxy for it, implementing the given interfaces.
168
      if (ObjectUtils.isEmpty(scriptInterfaces)) {
×
169
        throw new IllegalArgumentException("Given script requires a script proxy: " +
×
170
                "At least one script interface is required.\nScript: " + scriptSource);
171
      }
172
      XThis xt = (XThis) interpreter.eval("return this");
×
173
      return Proxy.newProxyInstance(classLoader, scriptInterfaces, new BshObjectInvocationHandler(xt));
×
174
    }
175
  }
176

177
  /**
178
   * InvocationHandler that invokes a BeanShell script method.
179
   */
180
  private record BshObjectInvocationHandler(XThis xt) implements InvocationHandler {
×
181

182
    @Override
183
    @Nullable
184
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
185
      if (ReflectionUtils.isEqualsMethod(method)) {
×
186
        return (isProxyForSameBshObject(args[0]));
×
187
      }
188
      else if (ReflectionUtils.isHashCodeMethod(method)) {
×
189
        return this.xt.hashCode();
×
190
      }
191
      else if (ReflectionUtils.isToStringMethod(method)) {
×
192
        return "BeanShell object [%s]".formatted(this.xt);
×
193
      }
194
      try {
195
        Object result = this.xt.invokeMethod(method.getName(), args);
×
196
        if (result == Primitive.NULL || result == Primitive.VOID) {
×
197
          return null;
×
198
        }
199
        if (result instanceof Primitive) {
×
200
          return ((Primitive) result).getValue();
×
201
        }
202
        return result;
×
203
      }
204
      catch (EvalError ex) {
×
205
        throw new BshExecutionException(ex);
×
206
      }
207
    }
208

209
    private boolean isProxyForSameBshObject(Object other) {
210
      if (!Proxy.isProxyClass(other.getClass())) {
×
211
        return false;
×
212
      }
213
      InvocationHandler ih = Proxy.getInvocationHandler(other);
×
214
      return (ih instanceof BshObjectInvocationHandler &&
×
215
              this.xt.equals(((BshObjectInvocationHandler) ih).xt));
×
216
    }
217
  }
218

219
  /**
220
   * Exception to be thrown on script execution failure.
221
   */
222
  public static final class BshExecutionException extends NestedRuntimeException {
223

224
    private BshExecutionException(EvalError ex) {
225
      super("BeanShell script execution failed", ex);
×
226
    }
×
227
  }
228

229
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc