-
Notifications
You must be signed in to change notification settings - Fork 183
/
NameSpace.java
1626 lines (1514 loc) · 66.9 KB
/
NameSpace.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*****************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
* *
* *
* This file is part of the BeanShell Java Scripting distribution. *
* Documentation and updates may be found at http://www.beanshell.org/ *
* Patrick Niemeyer (pat@pat.net) *
* Author of Learning Java, O'Reilly & Associates *
* *
*****************************************************************************/
package bsh;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static bsh.Interpreter.COMPATIBILITY_STRICT_JAVA;
import static bsh.Interpreter.COMPATIBILITY_BSH2_VARIABLE_SCOPING;
/** A namespace in which methods, variables, and imports (class names) live.
* This is package public because it is used in the implementation of some bsh
* commands. However for normal use you should be using methods on
* bsh.Interpreter to interact with your scripts.
* <p>
* A bsh.This object is a thin layer over a NameSpace that associates it with an
* Interpreter instance. Together they comprise a Bsh scripted object context.
* <p>
* Note: Dropping support for JDK 1.1. in favour of Collections
* Note: This class has gotten too big. It should be broken down a bit. */
public class NameSpace
implements Serializable, BshClassManager.Listener, NameSource, Cloneable {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The Constant JAVACODE. */
public static final NameSpace JAVACODE =
new NameSpace(null, null, "Called from compiled Java code.");
static {
JAVACODE.isMethod = true;
}
// Begin instance data
// Note: if we add something here we should reset it in the clear() method.
/** The name of this namespace. If the namespace is a method body namespace
* then this is the name of the method. If it's a class or class instance
* then it's the name of the class. */
private String nsName;
/** The parent. */
private NameSpace parent;
/** The variables. */
private Map<String, Variable> variables = new HashMap<>();
/** The methods. */
private Map<String, List<BshMethod>> methods = new HashMap<>();
/** The imported classes. */
protected Map<String, String> importedClasses = new HashMap<>();
/** The imported packages. */
private List<String> importedPackages = new ArrayList<>();
/** The imported commands. */
private List<String> importedCommands = new ArrayList<>();
/** The imported objects. */
private List<Object> importedObjects = new ArrayList<>();
/** The imported static. */
private List<Class<?>> importedStatic = new ArrayList<>();
/** The name source listeners. */
private List<NameSource.Listener> nameSourceListeners = new ArrayList<>();
/** The package name. */
private String packageName;
/** The class manager. */
private transient BshClassManager classManager;
/** The this reference. */
// See notes in getThis()
private This thisReference;
/** Name resolver objects. */
private Map<String, Name> names = new HashMap<>();
/** The node associated with the creation of this namespace. This is used
* support getInvocationLine() and getInvocationText(). */
Node callerInfoNode;
/** Note that the namespace is a method body namespace. This is used for
* printing stack traces in exceptions. */
boolean isMethod;
/** Note that the namespace is a class body or class instance namespace.
* This is used for controlling static/object import precedence, etc. Note:
* We can move this class related behavior out to a subclass of NameSpace,
* but we'll start here. */
boolean isClass;
boolean isInterface;
boolean isEnum;
/** The class static. */
Class<?> classStatic;
/** The class instance. */
Object classInstance;
/** Local class cache for classes resolved through this namespace using
* getClass() (taking into account imports). Only unqualified class names
* are cached here (those which might be imported). Qualified names are
* always absolute and are cached by BshClassManager. */
private transient Map<String, Class<?>> classCache = new HashMap<>();
/** Sets the class static.
* @param clas the new class static */
void setClassStatic(final Class<?> clas) {
this.classStatic = clas;
this.importStatic(clas);
}
/** Sets the class instance.
* @param instance the new class instance */
void setClassInstance(final Object instance) {
this.classInstance = instance;
this.importObject(instance);
}
/** Gets the class instance.
* @return the class instance
* @throws UtilEvalError the util eval error */
Object getClassInstance() throws UtilEvalError {
if (this.classInstance != null)
return this.classInstance;
if (this.classStatic != null)
throw new UtilEvalError(
"Can't refer to class instance from static context.");
else
throw new InterpreterError(
"Can't resolve class instance 'this' in: " + this);
}
// End instance data
// Begin constructors
/** Instantiates a new name space.
* @param parent the parent
* @param name the name
* @parent the parent namespace of this namespace. Child namespaces inherit
* all variables and methods of their parent and can (of course)
* override / shadow them. */
public NameSpace(final NameSpace parent, final String name) {
// Note: in this case parent must have a class manager.
this(parent, null, name);
}
/** Instantiates a new name space for name.
* @param name the name */
public NameSpace(final String name) {
this(null, null, name);
}
/** Instantiates a new name space for name with class manager.
* @param name the name
* @param classManager the class manager */
public NameSpace(final String name, final BshClassManager classManager) {
this(null, classManager, name);
}
/** Instantiates a new name space.
* @param parent the parent
* @param classManager the class manager
* @param name the name */
public NameSpace(final NameSpace parent, final BshClassManager classManager,
final String name) {
// We might want to do this here rather than explicitly in Interpreter
// for global (see also prune())
// if (classManager == null && (parent == null))
// create our own class manager?
this.setName(name);
this.setParent(parent);
this.setClassManager(classManager);
// Register for notification of classloader change
this.getClassManager().addListener(this);
}
/** Sets the name.
* @param name the new name */
// End constructors
public void setName(final String name) {
this.nsName = name;
}
/** The name of this namespace. If the namespace is a method body namespace
* then this is the name of the method. If it's a class or class instance
* then it's the name of the class.
* @return the name */
public String getName() {
return this.nsName;
}
/** Set the node associated with the creation of this namespace. This is
* used in debugging and to support the getInvocationLine() and
* getInvocationText() methods.
* @param node the new node */
void setNode(final Node node) {
this.callerInfoNode = node;
}
/** Gets the node.
* @return the node */
Node getNode() {
if (this.callerInfoNode != null)
return this.callerInfoNode;
if (this.parent != null)
return this.parent.getNode();
else
return null;
}
/** Resolve name to an object through this namespace.
* @param name the name
* @param interpreter the interpreter
* @return the object
* @throws UtilEvalError the util eval error */
public Object get(final String name, final Interpreter interpreter)
throws UtilEvalError {
final CallStack callstack = new CallStack(this);
return this.getNameResolver(name).toObject(callstack, interpreter);
}
/** Set the variable through this namespace.
* <p>
* Note: this method is primarily intended for use internally. If you use
* this method outside of the bsh package and wish to set variables with
* primitive values you will have to wrap them using bsh.Primitive.
* </p>
* @param name the name
* @param value the value
* @param compatibilityFlags specifies optional modes such as strict
* java rules or V2 variable scoping
* @throws UtilEvalError the util eval error
* @see bsh.Primitive
* <p>
* Setting a new variable (which didn't exist before) or removing a
* variable causes a namespace change. </p>*/
public void setVariable(String name, Object value,
int compatibilityFlags) throws UtilEvalError {
setVariable(name, value, compatibilityFlags, true);
}
/** Set a variable explicitly in the local scope.
* @param name the name
* @param value the value
* @param compatibilityFlags specifies optional modes such as strict
* java rules or V2 variable scoping
* @return the variable
* @throws UtilEvalError the util eval error */
public Variable setLocalVariable(String name, Object value,
int compatibilityFlags)
throws UtilEvalError {
return setVariable(name, value, compatibilityFlags, false/* recurse */);
}
/** Set the value of a the variable 'name' through this namespace. The
* variable may be an existing or non-existing variable. It may live in this
* namespace or in a parent namespace if recurse is true.
* <p>
* Note: this method is primarily intended for use internally. If you use
* this method outside of the bsh package and wish to set variables with
* primitive values you will have to wrap them using bsh.Primitive.
* </p>
* @param name the name
* @param value the value
* @param compatibilityFlags specifies optional modes such as strict
* java rules or V2 variable scoping
* @param recurse determines whether we will search for the variable in our
* parent's scope before assigning locally.
* @return the variable
* @throws UtilEvalError the util eval error
* @see bsh.Primitive
* <p>
* Setting a new variable (which didn't exist before) or removing a
* variable causes a namespace change. </p>*/
Variable setVariable(String name, Object value,
int compatibilityFlags, boolean recurse)
throws UtilEvalError {
// primitives should have been wrapped
if (value == null)
value = Primitive.NULL; // So then wrap it
// Locate the variable definition if it exists.
final Variable existing = getVariableImpl(name, recurse);
// Found an existing variable here (or above if recurse allowed)
if (existing != null) {
existing.setValue( value, Variable.ASSIGNMENT );
return existing;
} else {
// No previous variable definition found here (or above if recurse)
/*
* Do not allow loosely type variable if strict mode is on
*/
if ((compatibilityFlags&COMPATIBILITY_STRICT_JAVA)==
COMPATIBILITY_STRICT_JAVA)
throw new UtilEvalError(
"(Strict Java mode) Assignment to undeclared variable: "
+ name);
final Variable var = this.createVariable(name, value,
null/* modifiers */);
this.variables.put(name, var);
this.nameSpaceChanged();
return var;
}
}
////////////////////////////////////////////////////////////////////////////
/**
* <p>
* Sets a variable or property. See "setVariable" for rules regarding
* scoping.
* </p>
* <p>
* We first check for the existence of the variable. If it exists, we set
* it. If the variable does not exist we look for a property. If the
* property exists and is writable we set it. Finally, if neither the
* variable or the property exist, we create a new variable.
* </p>
* @param name the name
* @param value the value
* @param compatibilityFlags specifies optional modes such as strict
* java rules or V2 variable scoping
* @throws UtilEvalError the util eval error */
public void setVariableOrProperty(String name, Object value,
int compatibilityFlags)
throws UtilEvalError {
setVariableOrProperty(name, value, compatibilityFlags, true);
}
/** Set a variable or property explicitly in the local scope.
* <p>
* Sets a variable or property. See "setLocalVariable" for rules regarding
* scoping.
* </p>
* <p>
* We first check for the existence of the variable. If it exists, we set
* it. If the variable does not exist we look for a property. If the
* property exists and is writable we set it. Finally, if neither the
* variable or the property exist, we create a new variable.
* </p>
* @param name the name
* @param value the value
* @param compatibilityFlags specifies optional modes such as strict
* java rules or V2 variable scoping
* @throws UtilEvalError the util eval error */
void setLocalVariableOrProperty(String name, Object value,
int compatibilityFlags)
throws UtilEvalError {
setVariableOrProperty(name, value, compatibilityFlags, false/* recurse */);
}
/** Set the value of a the variable or property 'name' through this
* namespace.
* <p>
* Sets a variable or property. See "setVariableOrProperty" for rules
* regarding scope.
* </p>
* <p>
* We first check for the existence of the variable. If it exists, we set
* it. If the variable does not exist we look for a property. If the
* property exists and is writable we set it. Finally, if neither the
* variable or the property exist, we create a new variable.
* </p>
* @param name the name
* @param value the value
* @param compatibilityFlags specifies optional modes such as strict
* java rules or V2 variable scoping
* @param recurse determines whether we will search for the variable in our
* parent's scope before assigning locally.
* @throws UtilEvalError the util eval error */
void setVariableOrProperty(String name, Object value,
int compatibilityFlags, boolean recurse)
throws UtilEvalError {
// primitives should have been wrapped
if (value == null)
throw new InterpreterError("null variable value");
// Locate the variable definition if it exists.
final Variable existing = this.getVariableImpl(name, recurse);
// Found an existing variable here (or above if recurse allowed)
if (existing != null)
try {
existing.setValue(value, Variable.ASSIGNMENT);
} catch (final UtilEvalError e) {
throw new UtilEvalError("Variable assignment: " + name +
": " + e.getMessage(), e);
}
else {
// No previous variable definition found here (or above if recurse)
/*
* If strict jave mode then loosely typed variables are not
* allowed
*/
if ((compatibilityFlags&COMPATIBILITY_STRICT_JAVA)==
COMPATIBILITY_STRICT_JAVA)
throw new UtilEvalError("(Strict Java mode) Assignment to undeclared variable: "
+ name);
/*
* Attempt to set the value as a property
*/
boolean setProp = this.attemptSetPropertyValue(name, value,
null != thisReference ? thisReference.declaringInterpreter
: null);
if (setProp)
return;
if ((compatibilityFlags&COMPATIBILITY_BSH2_VARIABLE_SCOPING)==
COMPATIBILITY_BSH2_VARIABLE_SCOPING) {
/*
* BeanShell V2 loose variable scoping allows new loose
* variables to be set in the outer scope.
*/
setVariable(name, value, compatibilityFlags, recurse);
} else {
/*
* BeanShell V3 creates loose variables in the
* current scope, similar to java declared variables.
*/
variables.put(name,
createVariable(name, value, null/* modifiers */));
nameSpaceChanged();
}
}
}
/** Creates the variable.
* @param name the name
* @param value the value
* @param mods the mods
* @return the variable
* @throws UtilEvalError the util eval error */
protected Variable createVariable(final String name, final Object value,
final Modifiers mods) throws UtilEvalError {
return this.createVariable(name, null/* type */, value, mods);
}
/** Creates the variable.
* @param name the name
* @param type the type
* @param value the value
* @param mods the mods
* @return the variable
* @throws UtilEvalError the util eval error */
protected Variable createVariable(final String name, final Class<?> type,
final Object value, final Modifiers mods) throws UtilEvalError {
return new Variable(name, type, value, mods);
}
/** Creates the variable.
* @param name the name
* @param type the type
* @param lhs the lhs
* @return the variable
* @throws UtilEvalError the util eval error */
protected Variable createVariable(final String name, final Class<?> type,
final LHS lhs) throws UtilEvalError {
return new Variable(name, type, lhs);
}
/** Remove the variable from the namespace.
* @param name the name */
public void unsetVariable(final String name) {
this.variables.remove(name);
this.nameSpaceChanged();
}
/**
Get the names of variables defined in this namespace.
(This does not show variables in parent namespaces).
*/
public String [] getVariableNames() {
return this.variables.keySet().stream().toArray(String[]::new);
}
/**
Get the variables defined in this namespace.
(This does not show variables in parent namespaces).
*/
public Variable [] getVariables() {
return this.variables.values().stream().toArray(Variable[]::new);
}
/**
Get the names of methods declared in this namespace.
(This does not include methods in parent namespaces).
*/
public String [] getMethodNames()
{
return this.methods.keySet().stream().toArray(String[]::new);
}
/** Get the methods defined in this namespace. (This does not show methods
* in parent namespaces). Note: This will probably be renamed
* getDeclaredMethods()
* @return the methods */
public BshMethod[] getMethods() {
return this.methods.values().stream()
.flatMap(v -> v.stream()).toArray(BshMethod[]::new);
}
/** Get the parent namespace. Note: this isn't quite the same as getSuper().
* getSuper() returns 'this' if we are at the root namespace.
* @return the parent */
public NameSpace getParent() {
return this.parent;
}
/** Check if this namespace is a child of the given namespace.
* @param parent the possible parent
* @return true if this is child of parent */
public boolean isChildOf(NameSpace parent) {
return null != this.getParent() && (
this.getParent().equals(parent)
|| this.getParent().isChildOf(parent));
}
/** Get the parent namespace' This reference or this namespace' This
* reference if we are the top.
* @param declaringInterpreter the declaring interpreter
* @return the super */
public This getSuper(final Interpreter declaringInterpreter) {
if (isClass && null != classStatic) { // get class super instance This
Class<?> zuper = classStatic.getSuperclass();
if (Reflect.isGeneratedClass(zuper))
return Reflect.getClassInstanceThis(classInstance, zuper.getSimpleName());
}
if (this.parent != null) {
if (this.parent.isClass)
return this.parent.getSuper(declaringInterpreter);
return this.parent.getThis(declaringInterpreter);
}
return this.getThis(declaringInterpreter);
}
/** Get the top level namespace or this namespace if we are the top. Note:
* this method should probably return type bsh.This to be consistent with
* getThis();
* @param declaringInterpreter the declaring interpreter
* @return the global */
public This getGlobal(final Interpreter declaringInterpreter) {
if (this.parent != null)
return this.parent.getGlobal(declaringInterpreter);
else
return this.getThis(declaringInterpreter);
}
/** A This object is a thin layer over a namespace, comprising a bsh object
* context. It handles things like the interface types the bsh object
* supports and aspects of method invocation on it.
* <p>
* The declaringInterpreter is here to support callbacks from Java through
* generated proxies. The scripted object "remembers" who created it for
* things like printing messages and other per-interpreter phenomenon when
* called externally from Java.
* @param declaringInterpreter the declaring interpreter
* @return the this Note: we need a singleton here so that things like 'this
* == this' work (and probably a good idea for speed). Caching a
* single instance here seems technically incorrect, considering the
* declaringInterpreter could be different under some circumstances.
* (Case: a child interpreter running a source() / eval() command).
* However the effect is just that the main interpreter that
* executes your script should be the one involved in call-backs
* from Java. I do not know if there are corner cases where a child
* interpreter would be the first to use a This reference in a
* namespace or if that would even cause any problems if it did...
* We could do some experiments to find out... and if necessary we
* could cache on a per interpreter basis if we had weak
* references... We might also look at skipping over child
* interpreters and going to the parent for the declaring
* interpreter, so we'd be sure to get the top interpreter. */
public This getThis(final Interpreter declaringInterpreter) {
if (this.thisReference == null)
this.thisReference = This.getThis(this, declaringInterpreter);
return this.thisReference;
}
/** Gets the class manager.
* @return the class manager */
public BshClassManager getClassManager() {
if (this.classManager != null)
return this.classManager;
if (this.parent != null && this.parent != JAVACODE)
return this.parent.getClassManager();
this.setClassManager(BshClassManager
.createClassManager(null/* interp */));
// Interpreter.debug("No class manager namespace:" +this);
return this.classManager;
}
/** Sets the class manager.
* @param classManager the new class manager */
void setClassManager(final BshClassManager classManager) {
this.classManager = classManager;
}
/** Used for serialization. */
public void prune() {
this.getClassManager();
this.setParent(null);
}
/** Sets the parent.
* @param parent the new parent */
public void setParent(final NameSpace parent) {
this.parent = parent;
// If we are disconnected from root we need to handle the def imports
if (parent == null)
this.loadDefaultImports();
}
/**
* <p>
* Get the specified variable or property in this namespace or a parent
* namespace.
* </p>
* <p>
* We first search for a variable name, and then a property.
* </p>
* @param name the name
* @param interp the interp
* @return The variable or property value or Primitive.VOID if neither is
* defined.
* @throws UtilEvalError the util eval error */
public Object getVariableOrProperty(final String name,
final Interpreter interp) throws UtilEvalError {
final Object val = this.getVariable(name, true);
return val == Primitive.VOID
? this.getPropertyValue(name, interp)
: val;
}
/** Get the specified variable in this namespace or a parent namespace.
* <p>
* Note: this method is primarily intended for use internally. If you use
* this method outside of the bsh package you will have to use
* Primitive.unwrap() to get primitive values.
* @param name the name
* @return The variable value or Primitive.VOID if it is not defined.
* @throws UtilEvalError the util eval error
* @see Primitive#unwrap(Object) */
public Object getVariable(final String name) throws UtilEvalError {
return this.getVariable(name, true);
}
/** Get the specified variable in this namespace.
* @param name the name
* @param recurse If recurse is true then we recursively search through
* parent namespaces for the variable.
* <p>
* Note: this method is primarily intended for use internally. If you
* use this method outside of the bsh package you will have to use
* Primitive.unwrap() to get primitive values.
* @return The variable value or Primitive.VOID if it is not defined.
* @throws UtilEvalError the util eval error
* @see Primitive#unwrap(Object) */
public Object getVariable(final String name, final boolean recurse)
throws UtilEvalError {
final Variable var = this.getVariableImpl(name, recurse);
Interpreter.debug("Get variable: ", name, " = ", var);
return this.unwrapVariable(var);
}
/** Locate a variable and return the Variable object with optional recursion
* through parent name spaces.
* <p/>
* If this namespace is static, return only static variables.
* @param name the name
* @param recurse the recurse
* @return the Variable value or null if it is not defined
* @throws UtilEvalError the util eval error */
protected Variable getVariableImpl(final String name, final boolean recurse)
throws UtilEvalError {
Variable var = null;
if (this.variables.containsKey(name))
return this.variables.get(name);
else
var = this.getImportedVar(name);
// try parent
if (recurse && var == null && this.parent != null)
var = this.parent.getVariableImpl(name, recurse);
return var;
}
protected void setVariableImpl(Variable var) {
if (!this.variables.containsKey(var.getName()))
this.variables.put(var.getName(), var);
}
/*
Get variables declared in this namespace.
*/
public Variable [] getDeclaredVariables()
{
return this.variables.values().stream().toArray(Variable[]::new);
}
/** Unwrap a variable to its value.
* @param var the var
* @return return the variable value. A null var is mapped to Primitive.VOID
* @throws UtilEvalError the util eval error */
protected Object unwrapVariable(final Variable var) throws UtilEvalError {
return var == null ? Primitive.VOID : var.getValue();
}
/** Sets the typed variable.
* @param name the name
* @param type the type
* @param value the value
* @param isFinal the is final
* @throws UtilEvalError the util eval error
* @deprecated See #setTypedVariable(String, Class, Object, Modifiers) */
@Deprecated
public void setTypedVariable(final String name, final Class<?> type,
final Object value, final boolean isFinal) throws UtilEvalError {
final Modifiers modifiers = new Modifiers(Modifiers.FIELD);
if (isFinal)
modifiers.addModifier("final");
this.setTypedVariable(name, type, value, modifiers);
}
/** Declare a variable in the local scope and set its initial value. Value
* may be null to indicate that we would like the default value for the
* variable type. (e.g. 0 for integer types, null for object types). An
* existing typed variable may only be set to the same type. If an untyped
* variable of the same name exists it will be overridden with the new typed
* var. The set will perform a Types.getAssignableForm() on the value if
* necessary.
* <p>
* Note: this method is primarily intended for use internally. If you use
* this method outside of the bsh package and wish to set variables with
* primitive values you will have to wrap them using bsh.Primitive.
* @param name the name
* @param type the type
* @param value If value is null, you'll get the default value for the type
* @param modifiers may be null
* @throws UtilEvalError the util eval error
* @see bsh.Primitive */
public void setTypedVariable(final String name, final Class<?> type,
final Object value, final Modifiers modifiers)
throws UtilEvalError {
// Setting a typed variable is always a local operation.
final Variable existing = this.getVariableImpl(name,
false/* recurse */);
// Null value is just a declaration
// Note: we might want to keep any existing value here instead of reset
// does the variable already exist? Is it typed?
if (existing != null && existing.getType() != null)
// If it had a different type throw error.
// This allows declaring the same var again, but not with
// a different (even if assignable) type.
if (existing.getType() != type)
throw new UtilEvalError("Typed variable: " + name
+ " was previously declared with type: "
+ existing.getType());
else {
if (existing.modifiers == null)
existing.modifiers = modifiers;
// else set it and return
existing.setValue(value, Variable.DECLARATION);
return;
}
// Add the new typed var
this.variables.put(name,
this.createVariable(name, type, value, modifiers));
}
/** Dissallow static vars outside of a class.
* @param name is here just to allow the error message to use it protected
* void checkVariableModifiers(String name, Modifiers modifiers)
* throws UtilEvalError { if (modifiers!=null &&
* modifiers.hasModifier("static")) throw new UtilEvalError("Can't
* declare static variable outside of class: "+name); }
* @param method the method
* @throws UtilEvalError the util eval error Note: this is primarily for
* internal use.
* @see Interpreter#source(String)
* @see Interpreter#eval(String) */
public void setMethod(BshMethod method) {
String name = method.getName();
if (!this.methods.containsKey(name))
this.methods.put(name, new ArrayList<BshMethod>(1));
this.methods.get(name).remove(method);
this.methods.get(name).add(0, method);
}
/** Gets the method.
* @param name the name
* @param sig the sig
* @return the method
* @throws UtilEvalError the util eval error
* @see #getMethod(String, Class [], boolean)
* @see #getMethod(String, Class []) */
public BshMethod getMethod(final String name, final Class<?>[] sig)
throws UtilEvalError {
return this.getMethod(name, sig, false/* declaredOnly */);
}
/** Get the bsh method matching the specified signature declared in this
* name space or a parent.
* <p>
* Note: this method is primarily intended for use internally. If you use
* this method outside of the bsh package you will have to be familiar with
* BeanShell's use of the Primitive wrapper class.
* @param name the name
* @param sig the sig
* @param declaredOnly if true then only methods declared directly in this
* namespace will be found and no inherited or imported methods will
* be visible.
* @return the BshMethod or null if not found
* @throws UtilEvalError the util eval error
* @see bsh.Primitive */
public BshMethod getMethod(final String name, final Class<?>[] sig,
final boolean declaredOnly) throws UtilEvalError {
BshMethod method = null;
Interpreter.debug("Get method: ", name, " ", this );
// Change import precedence if we are a class body/instance
// Get import first. Enum blocks may override class methods.
if (this.isClass && !this.isEnum && !declaredOnly)
method = this.getImportedMethod(name, sig);
if (method == null && this.methods.containsKey(name))
method = Reflect.findMostSpecificBshMethod(sig, methods.get(name));
if (method == null && !this.isClass && !declaredOnly)
method = this.getImportedMethod(name, sig);
// try parent
if (method == null && !declaredOnly && this.parent != null)
return this.parent.getMethod(name, sig);
return method;
}
/** Import a class name. Subsequent imports override earlier ones
* @param name the name */
public void importClass(final String name) {
this.importedClasses.put(Name.suffix(name, 1), name);
this.nameSpaceChanged();
}
/** subsequent imports override earlier ones.
* @param name the name */
public void importPackage(final String name) {
this.importedPackages.remove(name);
this.importedPackages.add(0, name);
this.nameSpaceChanged();
}
/** Import scripted or compiled BeanShell commands in the following package
* in the classpath. You may use either "/" path or "." package notation.
* e.g. importCommands("/bsh/commands") or importCommands("bsh.commands")
* are equivalent. If a relative path style specifier is used then it is
* made into an absolute path by prepending "/".
* @param name the name */
public void importCommands(String name) {
// dots to slashes
name = name.replace('.', '/');
// absolute
if (!name.startsWith("/"))
name = "/" + name;
// remove trailing (but preserve case of simple "/")
if (name.length() > 1 && name.endsWith("/"))
name = name.substring(0, name.length() - 1);
this.importedCommands.remove(name);
this.importedCommands.add(0, name);
this.nameSpaceChanged();
}
/** A command is a scripted method or compiled command class implementing a
* specified method signature. Commands are loaded from the classpath and
* may be imported using the importCommands() method.
* <p/>
* This method searches the imported commands packages for a script or
* command object corresponding to the name of the method. If it is a script
* the script is sourced into this namespace and the BshMethod for the
* requested signature is returned. If it is a compiled class the class is
* returned. (Compiled command classes implement static invoke() methods).
* <p/>
* The imported packages are searched in reverse order, so that later
* imports take priority. Currently only the first object (script or class)
* with the appropriate name is checked. If another, overloaded form, is
* located in another package it will not currently be found. This could be
* fixed.
* <p/>
* @param name is the name of the desired command method
* @param argTypes is the signature of the desired command method.
* @param interpreter the interpreter
* @return a BshMethod, Class, or null if no such command is found.
* @throws UtilEvalError if loadScriptedCommand throws UtilEvalError i.e. on
* errors loading a script that was found */
public Object getCommand(final String name, final Class<?>[] argTypes,
final Interpreter interpreter) throws UtilEvalError {
Interpreter.debug("Get command: ", name);
final BshClassManager bcm = interpreter.getClassManager();
// loop backwards for precedence
for (final String path : this.importedCommands) {
String scriptPath;
if (path.equals("/"))
scriptPath = path + name + ".bsh";
else
scriptPath = path + "/" + name + ".bsh";
Interpreter.debug("searching for script: " + scriptPath);
URL url = bcm.getResource(scriptPath);
if (null != url) try {
return this.loadScriptedCommand((InputStream) url.getContent(),
name, argTypes, scriptPath, interpreter);
} catch (IOException e) { /* ignore */ }
// Chop leading "/" and change "/" to "."
String className;
if (path.equals("/"))
className = name;
else
className = path.substring(1).replace('/', '.') + "."
+ name;
Interpreter.debug("searching for class: " + className);
final Class<?> clas = bcm.classForName(className);
if (clas != null)
return clas;
}
if (this.parent != null)
return this.parent.getCommand(name, argTypes, interpreter);
else
return null;
}
/** Gets the imported method.
* @param name the name
* @param sig the sig
* @return the imported method
* @throws UtilEvalError the util eval error */
protected BshMethod getImportedMethod(final String name, final Class<?>[] sig)
throws UtilEvalError {
// Try object imports
for (final Object object : this.importedObjects) {
final Invocable method = Reflect.resolveJavaMethod(
object.getClass(), name, sig, false/* onlyStatic */);
if (method != null)
return new BshMethod(method, object);
}
// Try static imports
for (final Class<?> stat : this.importedStatic) {
final Invocable method = Reflect.resolveJavaMethod(
stat, name, sig, true/* onlyStatic */);
if (method != null)
return new BshMethod(method, null/* object */);
}
return null;
}
/** Gets the imported var.
* @param name the name
* @return the imported var
* @throws UtilEvalError the util eval error */
protected Variable getImportedVar(final String name) throws UtilEvalError {
Variable var = null;
// Try object imports
for (final Object object : this.importedObjects) {
final Invocable field = Reflect.resolveJavaField(object.getClass(),
name, false/* onlyStatic */);
if (field != null)
var = this.createVariable(name, field.getReturnType(), new LHS(object, field));
else if (this.isClass) {
// try find inherited loose-typed instance fields
Class<?> supr = object.getClass();
while (Reflect.isGeneratedClass(supr = supr.getSuperclass())) {
This ths = Reflect.getClassInstanceThis(object, supr.getSimpleName());
if (null != ths && null != (var = ths.getNameSpace().variables.get(name)))
break;
}
}
if (null != var) {
this.variables.put(name, var);
return var;
}
}
// Try static imports
for (final Class<?> stat : this.importedStatic) {
final Invocable field = Reflect.resolveJavaField(stat,
name, true/* onlyStatic */);
if (field != null) {
var = this.createVariable(name, field.getReturnType(),
new LHS(field));
this.variables.put(name, var);
return var;
}
}