3636import java .util .ArrayList ;
3737import java .util .Collection ;
3838import java .util .HashMap ;
39- import java .util .LinkedHashSet ;
39+ import java .util .HashSet ;
4040import java .util .List ;
4141import java .util .Map ;
4242import java .util .Set ;
4646import org .jruby .embed .LocalVariableBehavior ;
4747import org .jruby .embed .variable .BiVariable ;
4848import org .jruby .embed .variable .VariableInterceptor ;
49+ import org .jruby .javasupport .JavaEmbedUtils ;
4950import org .jruby .runtime .DynamicScope ;
5051import org .jruby .runtime .builtin .IRubyObject ;
5152
@@ -76,6 +77,7 @@ public class BiVariableMap implements Map<String, Object> {
7677
7778 private List <String > varNames ;
7879 private List <BiVariable > variables ;
80+ private Map <String , IRubyObject > localVariables ;
7981
8082 /**
8183 * Constructs an empty map. Users do not instantiate this map. The map is created
@@ -173,11 +175,19 @@ private static String checkKey(final Object key) {
173175 * @param key is a key to be tested its presence
174176 * @return <code>true</code> if this map contains a mapping for the specified key
175177 */
176- @ Override
177- public boolean containsKey (final Object key ) {
178- if ( varNames == null || key == null ) return false ;
179- return varNames .contains ( checkKey (key ) );
180- }
178+ @ Override
179+ public boolean containsKey (final Object key ) {
180+ return containsKey (null , key );
181+ }
182+
183+ public boolean containsKey (final Object receiver , final Object key ) {
184+ final String name = checkKey (key );
185+ final VariableAccessor acc = VariableAccessor .getInstance (getLocalVariableBehavior (), name );
186+ if (acc == null )
187+ return false ;
188+ final IRubyObject rec = getReceiver (receiver );
189+ return acc .containsKey (this , rec , name );
190+ }
181191
182192 /**
183193 * Returns <code>true</code> if this map maps one or more keys to the
@@ -219,20 +229,26 @@ public Object get(Object key) {
219229 * @return the value in simple Java object to which the specified key is mapped, or
220230 * {@code null} if this map contains no mapping for the key
221231 */
222- public Object get (Object receiver , Object key ) {
223- checkKey (key );
224- final RubyObject robj = getReceiverObject (receiver );
225- // attemps to retrieve global variables
226- if ( isLazy () ) {
227- VariableInterceptor .tryLazyRetrieval (context .getLocalVariableBehavior (), this , robj , key );
228- }
229- BiVariable var = getVariable (robj , (String ) key );
230- return var == null ? null : var .getJavaObject ();
231- }
232-
233- private RubyObject getReceiverObject (final Object receiver ) {
234- return receiver instanceof RubyObject ? (RubyObject ) receiver : getTopSelf ();
235- }
232+ public Object get (Object receiver , Object key ) {
233+ final String name = checkKey (key );
234+ final VariableAccessor acc = VariableAccessor .getInstance (getLocalVariableBehavior (), name );
235+ if (acc == null )
236+ return null ;
237+ final IRubyObject rec = getReceiver (receiver );
238+ return toJava (acc .retrieve (this , rec , name ));
239+ }
240+
241+ private Object toJava (final IRubyObject value ) {
242+ return value != null ? value .toJava (Object .class ) : null ;
243+ }
244+
245+ private IRubyObject getReceiver (final Object receiver ) {
246+ if (receiver == null )
247+ return null ;
248+ if (receiver instanceof IRubyObject rec )
249+ return rec ;
250+ throw new IllegalArgumentException ("receiver object is not a Ruby object" );
251+ }
236252
237253 private RubyObject getTopSelf () {
238254 return (RubyObject ) getRuntime ().getTopSelf ();
@@ -324,66 +340,94 @@ public Object put(String key, Object value) {
324340 * @return the previous value associated with <code>key</code>, or
325341 * <code>null</code> if there was no mapping for <code>key</code>.
326342 */
327- public Object put (Object receiver , String key , Object value ) {
328- checkKey (key );
329- final RubyObject robj = getReceiverObject (receiver );
330- BiVariable var = getVariable (robj , key );
331- Object oldValue = null ;
332- if ( var != null ) { // updates
333- oldValue = var .getJavaObject ();
334- var .setJavaObject (robj .getRuntime (), value );
335- }
336- else { // creates new value
337- var = VariableInterceptor .getVariableInstance (context .getLocalVariableBehavior (), robj , key , value );
338- if ( var != null ) update (key , var );
339- }
340- return oldValue ;
341- }
343+ public Object put (Object receiver , String key , Object value ) {
344+ final String name = checkKey (key );
345+ final VariableAccessor acc = VariableAccessor .getInstance (getLocalVariableBehavior (), name );
346+ if (acc == null )
347+ return null ;
348+ final IRubyObject rec = getReceiver (receiver );
349+ final IRubyObject prevValue = acc .retrieve (this , rec , name );
350+ final IRubyObject rubyValue = value instanceof IRubyObject ? (IRubyObject ) value
351+ : JavaEmbedUtils .javaToRuby (getRuntime (), value );
352+ acc .inject (this , rec , name , rubyValue );
353+ return toJava (prevValue );
354+ }
342355
343356 /**
344357 * Returns Ruby's local variable names this map has. The returned array is mainly
345358 * used to inject local variables to Ruby scripts while parsing.
346359 *
347360 * @return String array of Ruby's local variable names
348361 */
349- public String [] getLocalVarNames () {
350- if ( variables == null ) return EMPTY_STRING_ARRAY ;
351-
352- List <String > localVarNames = new ArrayList <>(variables .size ());
353- for ( final BiVariable var : variables ) {
354- if ( var .getType () == BiVariable .Type .LocalVariable ) {
355- localVarNames .add ( var .getName () );
356- }
357- }
358- return localVarNames .toArray (new String [localVarNames .size ()]);
359- }
360-
362+ public String [] getLocalVarNames () {
363+ if (localVariables == null )
364+ return EMPTY_STRING_ARRAY ;
365+ return localVariables .keySet ().toArray (String []::new );
366+ }
367+
368+ Set <String > getLocalVariableNames () {
369+ if (localVariables == null )
370+ return Set .of ();
371+ return localVariables .keySet ();
372+ }
373+
374+ IRubyObject getLocalVariable (final String name ) {
375+ if (localVariables == null )
376+ return null ;
377+ return localVariables .get (name );
378+ }
379+
380+ void setLocalVariable (final String name , final IRubyObject value ) {
381+ if (localVariables == null )
382+ localVariables = new HashMap <String , IRubyObject >();
383+ localVariables .put (name , value );
384+ }
385+
386+ void removeLocalVariable (final String name ) {
387+ if (localVariables != null )
388+ localVariables .remove (name );
389+ }
390+
361391 /**
362392 * Returns Ruby's local variable values this map has. The returned array is
363393 * mainly used to inject local variables to Ruby scripts while evaluating.
364394 *
365395 * @return IRubyObject array of Ruby's local variable names.
366396 */
367- public IRubyObject [] getLocalVarValues () {
368- if ( variables == null ) return IRubyObject .NULL_ARRAY ;
369-
370- List <IRubyObject > localVarValues = new ArrayList <>(variables .size ());
371- for ( final BiVariable var : variables ) {
372- if ( var .getType () == BiVariable .Type .LocalVariable ) {
373- localVarValues .add ( var .getRubyObject () );
374- }
375- }
376- return localVarValues .toArray ( new IRubyObject [ localVarValues .size () ] );
377- }
378-
379- void inject (final DynamicScope scope ) {
380- VariableInterceptor .inject (this , scope );
381- }
382-
383- void retrieve (final IRubyObject receiver ) {
384- final RubyObject robj = getReceiverObject (receiver );
385- VariableInterceptor .retrieve (getLocalVariableBehavior (), this , robj );
386- }
397+ @ Deprecated
398+ public IRubyObject [] getLocalVarValues () {
399+ if (localVariables == null )
400+ return IRubyObject .NULL_ARRAY ;
401+ return localVariables .values ().toArray (IRubyObject []::new );
402+ }
403+
404+ void inject (final DynamicScope scope ) {
405+ if (localVariables == null )
406+ return ;
407+ final String [] names = scope .getAllNamesInScope ();
408+ for (int i = 0 ; i < names .length ; i ++)
409+ if (localVariables .containsKey (names [i ]))
410+ scope .setValue (i , localVariables .get (names [i ]), 0 );
411+ }
412+
413+ void retrieve (final IRubyObject receiver ) {
414+ if (persistLocalVariables (getLocalVariableBehavior ())) {
415+ final DynamicScope scope = (DynamicScope ) receiver .getRuntime ().getCurrentContext ().getCurrentScope ();
416+ final String [] names = scope .getAllNamesInScope ();
417+ for (int i = 0 ; i < names .length ; i ++)
418+ setLocalVariable (names [i ], scope .getValue (i , 0 ));
419+ }
420+ }
421+
422+ private boolean persistLocalVariables (LocalVariableBehavior localVariableBehavior ) {
423+ switch (getLocalVariableBehavior ()) {
424+ case BSF :
425+ case PERSISTENT :
426+ return true ;
427+ default :
428+ return false ;
429+ }
430+ }
387431
388432 void terminate () {
389433 VariableInterceptor .terminateGlobalVariables (getLocalVariableBehavior (), getVariables (), getRuntime ());
@@ -402,7 +446,7 @@ void terminate() {
402446 */
403447 @ Override
404448 public Object remove (final Object key ) {
405- return removeFrom (getTopSelf () , key );
449+ return removeFrom (null , key );
406450 }
407451
408452 /**
@@ -416,22 +460,16 @@ public Object remove(final Object key) {
416460 * @return the previous value associated with <code>key</code>, or
417461 * <code>null</code> if there was no mapping for <code>key</code>.
418462 */
419- public Object removeFrom (final Object receiver , final Object key ) {
420- if ( variables == null ) return null ;
421- checkKey (key );
422- final RubyObject robj = getReceiverObject (receiver );
423- for ( int i = 0 ; i < size (); i ++ ) {
424- if ( key .equals ( varNames .get (i ) ) ) {
425- final BiVariable var = variables .get (i );
426- if ( var .isReceiverIdentical (robj ) ) {
427- varNames .remove (i );
428- variables .remove (i );
429- return var .getJavaObject ();
430- }
431- }
432- }
433- return null ;
434- }
463+ public Object removeFrom (final Object receiver , final Object key ) {
464+ final String name = checkKey (key );
465+ final VariableAccessor acc = VariableAccessor .getInstance (getLocalVariableBehavior (), name );
466+ if (acc == null )
467+ return null ;
468+ final IRubyObject rec = getReceiver (receiver );
469+ final IRubyObject prevValue = acc .retrieve (this , rec , name );
470+ acc .remove (this , rec , name );
471+ return toJava (prevValue );
472+ }
435473
436474 /**
437475 * Copies all of the mappings from the specified map to this map.
@@ -488,10 +526,18 @@ public void clear() {
488526 *
489527 * @return a set view of the keys contained in this map
490528 */
491- @ Override
492- public Set <String > keySet () {
493- return new LinkedHashSet <String >( getNames () );
494- }
529+ @ Override
530+ public Set <String > keySet () {
531+ return keySet (null );
532+ }
533+
534+ public Set <String > keySet (final Object receiver ) {
535+ final IRubyObject rec = getReceiver (receiver );
536+ final Set <String > names = new HashSet <>();
537+ for (final VariableAccessor acc : VariableAccessor .getAll (getLocalVariableBehavior ()))
538+ names .addAll (acc .keySet (this , rec ));
539+ return names ;
540+ }
495541
496542 /**
497543 * Returns a {@link Collection} view of the values contained in this map.
0 commit comments