Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: bsh/Name.java


1   /*****************************************************************************
2    *                                                                           *
3    *  This file is part of the BeanShell Java Scripting distribution.          *
4    *  Documentation and updates may be found at http://www.beanshell.org/      *
5    *                                                                           *
6    *  Sun Public License Notice:                                               *
7    *                                                                           *
8    *  The contents of this file are subject to the Sun Public License Version  *
9    *  1.0 (the "License"); you may not use this file except in compliance with *
10   *  the License. A copy of the License is available at http://www.sun.com    * 
11   *                                                                           *
12   *  The Original Code is BeanShell. The Initial Developer of the Original    *
13   *  Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright     *
14   *  (C) 2000.  All Rights Reserved.                                          *
15   *                                                                           *
16   *  GNU Public License Notice:                                               *
17   *                                                                           *
18   *  Alternatively, the contents of this file may be used under the terms of  *
19   *  the GNU Lesser General Public License (the "LGPL"), in which case the    *
20   *  provisions of LGPL are applicable instead of those above. If you wish to *
21   *  allow use of your version of this file only under the  terms of the LGPL *
22   *  and not to allow others to use your version of this file under the SPL,  *
23   *  indicate your decision by deleting the provisions above and replace      *
24   *  them with the notice and other provisions required by the LGPL.  If you  *
25   *  do not delete the provisions above, a recipient may use your version of  *
26   *  this file under either the SPL or the LGPL.                              *
27   *                                                                           *
28   *  Patrick Niemeyer (pat@pat.net)                                           *
29   *  Author of Learning Java, O'Reilly & Associates                           *
30   *  http://www.pat.net/~pat/                                                 *
31   *                                                                           *
32   *****************************************************************************/
33  
34  
35  package bsh;
36  
37  import java.lang.reflect.Array;
38  import java.util.Hashtable;
39  import java.io.*;
40  import java.lang.reflect.InvocationTargetException;
41  
42  /**
43    What's in a name?  I'll tell you...
44    Name() is a somewhat ambiguous thing in the grammar and so is this.
45    <p>
46    
47    This class is a name resolver.  It holds a possibly ambiguous dot 
48    separated name and reference to a namespace in which it allegedly lives.  
49    It provides methods that attempt to resolve the name to various types of 
50    entities: e.g. an Object, a Class, a declared scripted BeanShell method.
51    <p>
52  
53    Name objects are created by the factory method NameSpace getNameResolver(), 
54    which caches them subject to a class namespace change.  This means that 
55    we can cache information about various types of resolution here.
56    Currently very little if any information is cached.  However with a future
57    "optimize" setting that defeats certain dynamic behavior we might be able
58    to cache quite a bit.
59  */
60  /*
61    <strong>Implementation notes</strong>
62    <pre>
63    Thread safety: all of the work methods in this class must be synchronized
64    because they share the internal intermediate evaluation state.
65  
66    Note about invokeMethod():  We could simply use resolveMethod and return
67    the MethodInvoker (BshMethod or JavaMethod) however there is no easy way
68    for the AST (BSHMehodInvocation) to use this as it doesn't have type
69    information about the target to resolve overloaded methods.
70    (In Java, overloaded methods are resolved at compile time... here they
71    are, of necessity, dynamic).  So it would have to do what we do here
72    and cache by signature.  We now do that for the client in Reflect.java.
73  
74    Note on this.caller resolution:
75    Although references like these do work:
76  
77      this.caller.caller.caller...   // works
78  
79    the equivalent using successive calls:
80  
81      // does *not* work
82      for( caller=this.caller; caller != null; caller = caller.caller );
83  
84    is prohibited by the restriction that you can only call .caller on a 
85    literal  this or caller reference.  The effect is that magic caller 
86    reference only works through the current 'this' reference.
87    The real explanation is that This referernces do not really know anything
88    about their depth on the call stack.  It might even be hard to define
89    such a thing...
90  
91    For those purposes we provide :
92  
93      this.callstack
94  
95    </pre>
96  */
97  class Name implements java.io.Serializable
98  {
99    // These do not change during evaluation
100   public NameSpace namespace;
101   String value = null;
102   
103   // ---------------------------------------------------------
104   // The following instance variables mutate during evaluation and should
105   // be reset by the reset() method where necessary
106 
107   // For evaluation
108   /** Remaining text to evaluate */
109   private String evalName;
110   /** 
111     The last part of the name evaluated.  This is really only used for
112      this, caller, and super resolution.
113   */
114   private String lastEvalName;
115   private static String FINISHED = null; // null evalname and we're finished
116   private Object evalBaseObject;  // base object for current eval
117 
118   private int callstackDepth;    // number of times eval hit 'this.caller'
119 
120   //  
121   //  End mutable instance variables.
122   // ---------------------------------------------------------
123 
124   // Begin Cached result structures
125   // These are optimizations 
126 
127   // Note: it's ok to cache class resolution here because when the class
128   // space changes the namespace will discard cached names.
129 
130   /** 
131     The result is a class 
132   */
133   Class asClass;
134 
135   /** 
136     The result is a static method call on the following class 
137   */
138   Class classOfStaticMethod;
139 
140   // End Cached result structures
141 
142   private void reset() {
143     evalName = value;
144     evalBaseObject = null;
145     callstackDepth = 0;
146   }
147 
148   /**
149     This constructor should *not* be used in general. 
150     Use NameSpace getNameResolver() which supports caching.
151     @see NameSpace getNameResolver().
152   */
153   // I wish I could make this "friendly" to only NameSpace
154   Name( NameSpace namespace, String s )
155   {
156     this.namespace = namespace;
157     value = s;
158   }
159 
160   /**
161     Resolve possibly complex name to an object value.
162 
163     Throws EvalError on various failures.
164     A null object value is indicated by a Primitive.NULL.
165     A return type of Primitive.VOID comes from attempting to access
166     an undefined variable.
167 
168     Some cases:
169       myVariable
170       myVariable.foo
171       myVariable.foo.bar
172       java.awt.GridBagConstraints.BOTH
173       my.package.stuff.MyClass.someField.someField...
174 
175     Interpreter reference is necessary to allow resolution of 
176     "this.interpreter" magic field.
177     CallStack reference is necessary to allow resolution of 
178     "this.caller" magic field.
179     "this.callstack" magic field.
180   */
181   public Object toObject( CallStack callstack, Interpreter interpreter ) 
182     throws UtilEvalError
183   {
184     return toObject( callstack, interpreter, false );
185   }
186 
187   /**
188     @see toObject()
189     @param forceClass if true then resolution will only produce a class.
190     This is necessary to disambiguate in cases where the grammar knows
191     that we want a class; where in general the var path may be taken.
192   */
193   synchronized public Object toObject( 
194     CallStack callstack, Interpreter interpreter, boolean forceClass ) 
195     throws UtilEvalError
196   {
197     reset();
198 
199     Object obj = null;
200     while( evalName != null )
201       obj = consumeNextObjectField( 
202         callstack, interpreter, forceClass, false/*autoalloc*/  );
203 
204     if ( obj == null )
205       throw new InterpreterError("null value in toObject()");
206 
207     return obj;
208   }
209 
210   private Object completeRound( 
211     String lastEvalName, String nextEvalName, Object returnObject )
212   {
213     this.lastEvalName = lastEvalName;
214     this.evalName = nextEvalName;
215     this.evalBaseObject = returnObject;
216     return returnObject;
217   }
218 
219   /**
220     Get the next object by consuming one or more components of evalName.  
221     Often this consumes just one component, but if the name is a classname 
222     it will consume all of the components necessary to make the class 
223     identifier.
224   */
225   private Object consumeNextObjectField(   
226     CallStack callstack, Interpreter interpreter, 
227     boolean forceClass, boolean autoAllocateThis ) 
228     throws UtilEvalError
229   {
230     /*
231       Is it a simple variable name?
232       Doing this first gives the correct Java precedence for vars 
233       vs. imported class names (at least in the simple case - see
234       tests/precedence1.bsh).  It should also speed things up a bit.
235     */
236     if ( (evalBaseObject == null && !isCompound(evalName) )
237       && !forceClass ) 
238     {
239       Object obj = resolveThisFieldReference( 
240         callstack, namespace, interpreter, evalName, false );
241 
242       if ( obj != Primitive.VOID )
243         return completeRound( evalName, FINISHED, obj );
244     }
245 
246     /*
247       Is it a bsh script variable reference?
248       If we're just starting the eval of name (no base object)
249       or we're evaluating relative to a This type reference check.
250     */
251     String varName = prefix(evalName, 1);
252     if ( ( evalBaseObject == null || evalBaseObject instanceof This  )
253       && !forceClass ) 
254     {
255       if ( Interpreter.DEBUG ) 
256         Interpreter.debug("trying to resolve variable: " + varName);
257 
258       Object obj;
259       // switch namespace and special var visibility
260       if ( evalBaseObject == null ) {
261         obj = resolveThisFieldReference( 
262           callstack, namespace, interpreter, varName, false );
263       } else {
264         obj = resolveThisFieldReference( 
265           callstack, ((This)evalBaseObject).namespace, 
266           interpreter, varName, true );
267       }
268 
269       if ( obj != Primitive.VOID ) 
270       {
271         // Resolved the variable
272         if ( Interpreter.DEBUG ) 
273           Interpreter.debug( "resolved variable: " + varName + 
274           " in namespace: "+namespace);
275 
276         return completeRound( varName, suffix(evalName), obj );
277       }
278     }
279 
280     /*
281       Is it a class name?
282       If we're just starting eval of name try to make it, else fail.
283     */
284     if ( evalBaseObject == null ) 
285     {
286       if ( Interpreter.DEBUG ) 
287         Interpreter.debug( "trying class: " + evalName);
288       
289       /*
290         Keep adding parts until we have a class 
291       */
292       Class clas = null;
293       int i = 1;
294       String className = null;
295       for(; i <= countParts(evalName); i++)
296       {
297         className = prefix(evalName, i);
298         if ( (clas = namespace.getClass(className)) != null )
299           break;
300       }
301     
302       if ( clas != null )  {
303         return completeRound(
304           className,
305           suffix( evalName, countParts(evalName)-i ),
306           new ClassIdentifier(clas) 
307         );
308       }
309       // not a class (or variable per above)
310       if ( Interpreter.DEBUG ) 
311         Interpreter.debug( "not a class, trying var prefix "+evalName );
312     }
313 
314     // No variable or class found in 'this' type ref.
315     // if autoAllocateThis then create one; a child 'this'.
316     if ( ( evalBaseObject == null || evalBaseObject instanceof This  )
317       && !forceClass && autoAllocateThis )
318     {
319       NameSpace targetNameSpace = 
320         ( evalBaseObject == null ) ?  
321           namespace : ((This)evalBaseObject).namespace;
322       Object obj = new NameSpace( 
323         targetNameSpace, "auto: "+varName ).getThis( interpreter );
324       targetNameSpace.setVariable( varName, obj, false );
325       return completeRound( varName, suffix(evalName), obj );
326     }
327 
328     /*
329       If we didn't find a class or variable name (or prefix) above
330       there are two possibilities:
331 
332       - If we are a simple name then we can pass as a void variable 
333       reference.
334       - If we are compound then we must fail at this point.
335     */
336     if ( evalBaseObject == null ) {
337       if ( !isCompound(evalName) ) {
338         return completeRound( evalName, FINISHED, Primitive.VOID );
339       } else
340         throw new UtilEvalError(
341           "Class or variable not found: " + evalName);
342     }
343 
344     /*
345       --------------------------------------------------------
346       After this point we're definitely evaluating relative to
347       a base object.
348       --------------------------------------------------------
349     */
350 
351     /*
352       Do some basic validity checks.
353     */
354 
355     if ( evalBaseObject == Primitive.NULL) // previous round produced null
356       throw new UtilTargetError( new NullPointerException( 
357         "Null Pointer while evaluating: " +value ) );
358 
359     if ( evalBaseObject == Primitive.VOID) // previous round produced void
360       throw new UtilEvalError(
361         "Undefined variable or class name while evaluating: "+value);
362 
363     if ( evalBaseObject instanceof Primitive)
364       throw new UtilEvalError("Can't treat primitive like an object. "+
365       "Error while evaluating: "+value);
366 
367     /* 
368       Resolve relative to a class type
369       static field, inner class, ?
370     */
371     if ( evalBaseObject instanceof ClassIdentifier ) 
372     {
373       Class clas = ((ClassIdentifier)evalBaseObject).getTargetClass();
374       String field = prefix(evalName, 1);
375 
376       Object obj = null;
377       // static field?
378       try {
379         if ( Interpreter.DEBUG ) 
380           Interpreter.debug("Name call to getStaticField, class: " 
381             +clas+", field:"+field);
382         obj = Reflect.getStaticField(clas, field);
383       } catch( ReflectError e ) { 
384         if ( Interpreter.DEBUG ) 
385           Interpreter.debug("field reflect error: "+e);
386       }
387 
388       // inner class?
389       if ( obj == null ) {
390         String iclass = clas.getName()+"$"+field;
391         Class c = namespace.getClass( iclass );
392         if ( c != null )
393           obj = new ClassIdentifier(c);
394       }
395 
396       if ( obj == null )
397         throw new UtilEvalError(
398           "No static field or inner class: " + field + " of " + clas);
399 
400       return completeRound( field, suffix(evalName), obj );
401     }
402 
403     /*
404       If we've fallen through here we are no longer resolving to
405       a class type.
406     */
407     if ( forceClass )
408       throw new UtilEvalError( 
409         value +" does not resolve to a class name." );
410 
411     /* 
412       Some kind of field access?
413     */
414 
415     String field = prefix(evalName, 1);
416 
417     // length access on array? 
418     if ( field.equals("length") && evalBaseObject.getClass().isArray() )
419     {
420       Object obj = new Primitive(Array.getLength(evalBaseObject));
421       return completeRound( field, suffix(evalName), obj );
422     }
423 
424     // Check for field on object 
425     // Note: could eliminate throwing the exception somehow
426     try {
427       Object obj = Reflect.getObjectField(evalBaseObject, field);
428       return completeRound( field, suffix(evalName), obj );
429     } catch(ReflectError e) { /* not a field */ }
430   
431     // if we get here we have failed
432     throw new UtilEvalError(
433       "Cannot access field: " + field + ", on object: " + evalBaseObject);
434   }
435 
436   /**
437     Resolve a variable relative to a This reference.
438 
439     This is the general variable resolution method, accomodating special
440     fields from the This context.  Together the namespace and interpreter
441     comprise the This context.  The callstack, if available allows for the
442     this.caller construct.  
443     Optionally interpret special "magic" field names: e.g. interpreter.
444     <p/>
445 
446     @param callstack may be null, but this is only legitimate in special
447     cases where we are sure resolution will not involve this.caller.
448 
449     @param namespace the namespace of the this reference (should be the
450     same as the top of the stack?
451   */
452   Object resolveThisFieldReference( 
453     CallStack callstack, NameSpace thisNameSpace, Interpreter interpreter, 
454     String varName, boolean specialFieldsVisible ) 
455     throws UtilEvalError
456   {
457     Object obj = null;
458 
459     if ( varName.equals("this") ) 
460     {
461       /*
462         Somewhat of a hack.  If the special fields are visible (we're
463         operating relative to a 'this' type already) dissallow further
464         .this references to prevent user from skipping to things like
465         super.this.caller
466       */
467       if ( specialFieldsVisible )
468         throw new UtilEvalError("Redundant to call .this on This type");
469 
470       // Allow getThis() to work through BlockNameSpace to the method
471       // namespace
472       This ths = thisNameSpace.getThis( interpreter );
473       thisNameSpace= ths.getNameSpace();
474 
475       /*
476         The following test handles the case of a scripted method in a
477         scripted class instance namespace.  This should really be in a
478         subclass of Name or NameSpace.  A reference to 'this' from
479         inside a method in a class instance should refer to the
480         enclosing class instance, as in Java.
481       */
482       if ( thisNameSpace.isMethod 
483         && thisNameSpace.getParent() != null 
484         && thisNameSpace.getParent() instanceof ClassNameSpace
485         && ((ClassNameSpace)(thisNameSpace.getParent()))
486           .isClassInstance()
487       )
488         ths = thisNameSpace.getParent().getThis( interpreter );
489 
490       return ths;
491     }
492 
493     /*
494       Some duplication for "super".  See notes for "this" above
495       If we're in an enclsing class instance and have a superclass
496       instance our super is the superclass instance.
497     */
498     if ( varName.equals("super") ) 
499     {
500       //if ( specialFieldsVisible )
501         //throw new UtilEvalError("Redundant to call .this on This type");
502 
503       // Allow getSuper() to through BlockNameSpace to the method's super
504       This ths = thisNameSpace.getSuper().getThis(interpreter);
505       thisNameSpace = ths.getNameSpace();
506       // super is now the closure's super or class instance
507 
508       // If we're a class instance and the parent is also a class instance
509       // then super means our parent.
510       if ( 
511         thisNameSpace instanceof ClassNameSpace
512         && ((ClassNameSpace)thisNameSpace).isClassInstance()
513 
514         && thisNameSpace.getParent() != null 
515         && thisNameSpace.getParent() instanceof ClassNameSpace
516         && ((ClassNameSpace)(thisNameSpace.getParent()))
517           .isClassInstance()
518       )
519         ths = thisNameSpace.getParent().getThis( interpreter );
520 
521       return ths;
522     }
523 
524     if ( varName.equals("global") )
525       obj = thisNameSpace.getGlobal().getThis( interpreter );
526 
527     if ( obj == null && specialFieldsVisible ) 
528     {
529       if (varName.equals("namespace"))
530         obj = thisNameSpace;
531       else if (varName.equals("variables"))
532         obj = thisNameSpace.getVariableNames();
533       else if (varName.equals("methods"))
534         obj = thisNameSpace.getMethodNames();
535       else if ( varName.equals("interpreter") )
536         if ( lastEvalName.equals("this") )
537           obj = interpreter;
538         else
539           throw new UtilEvalError(
540             "Can only call .interpreter on literal 'this'");
541     }
542 
543     if ( obj == null && specialFieldsVisible && varName.equals("caller") )
544     {
545       if ( lastEvalName.equals("this") || lastEvalName.equals("caller") ) 
546       {
547         // get the previous context (see notes for this class)
548         if ( callstack == null )
549           throw new InterpreterError("no callstack");
550         obj = callstack.get( ++callstackDepth ).getThis( 
551           interpreter ); 
552       }
553       else
554         throw new UtilEvalError(
555         "Can only call .caller on literal 'this' or literal '.caller'");
556 
557       // early return
558       return obj;
559     }
560 
561     if ( obj == null && specialFieldsVisible 
562       && varName.equals("callstack") )
563     {
564       if ( lastEvalName.equals("this") ) 
565       {
566         // get the previous context (see notes for this class)
567         if ( callstack == null )
568           throw new InterpreterError("no callstack");
569         obj = callstack;
570       }
571       else
572         throw new UtilEvalError(
573         "Can only call .callstack on literal 'this'");
574     }
575 
576 
577     if ( obj == null )
578       obj = thisNameSpace.getVariable(varName);
579 
580     return obj;
581   }
582 
583   /**
584     Check the cache, else use toObject() to try to resolve to a class
585     identifier.  
586 
587     @throws ClassNotFoundException on class not found.
588     @throws ClassPathException (type of EvalError) on special case of 
589     ambiguous unqualified name after super import. 
590   */
591   synchronized public Class toClass() 
592     throws ClassNotFoundException, UtilEvalError
593   {
594     if ( asClass != null )
595       return asClass;
596 
597     reset();
598 
599     // "var" means untyped, return null class
600     if ( evalName.equals("var") )
601       return asClass = null;
602 
603     /* Try straightforward class name first */
604     Class clas = namespace.getClass( evalName );
605 
606     if ( clas == null ) 
607     {
608       /* 
609         Try toObject() which knows how to work through inner classes
610         and see what we end up with 
611       */
612       Object obj = null;
613       try {
614         // Null interpreter and callstack references.
615         // class only resolution should not require them.
616         obj = toObject( null, null, true );  
617       } catch ( UtilEvalError  e ) { }; // couldn't resolve it
618     
619       if ( obj instanceof ClassIdentifier )
620         clas = ((ClassIdentifier)obj).getTargetClass();
621     }
622 
623     if ( clas == null )
624       throw new ClassNotFoundException(
625         "Class: " + value+ " not found in namespace");
626 
627     asClass = clas;
628     return asClass;
629   }
630 
631   /*
632   */
633   synchronized public LHS toLHS( 
634     CallStack callstack, Interpreter interpreter )
635     throws UtilEvalError
636   {
637     // Should clean this up to a single return statement
638     reset();
639     LHS lhs;
640 
641     // Simple (non-compound) variable assignment e.g. x=5;
642     if ( !isCompound(evalName) ) 
643     {
644       // Interpreter.debug("Simple var LHS...");
645       lhs = new LHS( namespace, evalName, false/*bubble up if allowed*/);
646       return lhs;
647     }
648 
649     // Field e.g. foo.bar=5;
650     Object obj = null;
651     try {
652       while( evalName != null && isCompound( evalName ) )
653         obj = consumeNextObjectField( callstack, interpreter, 
654           false/*forcclass*/, true/*autoallocthis*/ );
655     } 
656     catch( UtilEvalError e ) {
657       throw new UtilEvalError("LHS evaluation: " + e);
658     }
659 
660     //if ( obj instanceof ClassIdentifier )
661     // Finished eval and its a class.
662     if ( evalName == null && obj instanceof ClassIdentifier )
663       throw new UtilEvalError("Can't assign to class: " + value );
664     if ( obj == null )
665       throw new UtilEvalError("Error in LHS: " + value );
666 
667     // e.g. this.x=5;  or someThisType.x=5;
668     if ( obj instanceof This )
669     {
670       // dissallow assignment to magic fields
671       if ( 
672         evalName.equals("namespace")
673         || evalName.equals("variables")
674         || evalName.equals("methods")
675         || evalName.equals("caller")
676       )
677         throw new UtilEvalError(
678           "Can't assign to special variable: "+evalName );
679 
680       Interpreter.debug("found This reference evaluating LHS");
681       /*
682         If this was a literal "super" reference then we allow recursion
683         in setting the variable to get the normal effect of finding the
684         nearest definition starting at the super scope.  On any other
685         resolution qualified by a 'this' type reference we want to set
686         the variable directly in that scope. e.g. this.x=5;  or 
687         someThisType.x=5;
688         
689         In the old scoping rules super didn't do this.
690       */
691       boolean localVar = !lastEvalName.equals("super");
692       return new LHS( ((This)obj).namespace, evalName, localVar );
693     }
694 
695     if ( evalName != null )
696     {
697       try {
698         if ( obj instanceof ClassIdentifier ) 
699         {
700           Class clas = ((ClassIdentifier)obj).getTargetClass();
701           lhs = Reflect.getLHSStaticField(clas, evalName);
702           return lhs;
703         } else {
704           lhs = Reflect.getLHSObjectField(obj, evalName);
705           return lhs;
706         }
707       } catch(ReflectError e) {
708         throw new UtilEvalError("Field access: "+e);
709       }
710     }
711 
712     throw new InterpreterError("Internal error in lhs...");
713   }
714   
715     /**
716     Invoke the method identified by this name.
717     Performs caching of method resolution using SignatureKey.
718     <p>
719 
720         Name contains a wholely unqualfied messy name; resolve it to 
721     ( object | static prefix ) + method name and invoke.
722     <p>
723 
724         The interpreter is necessary to support 'this.interpreter' references
725     in the called code. (e.g. debug());
726     <p>
727 
728     <pre>
729         Some cases:
730 
731             // dynamic
732             local();
733             myVariable.foo();
734             myVariable.bar.blah.foo();
735             // static
736             java.lang.Integer.getInteger("foo");
737     </pre>
738     */
739     public Object invokeMethod(
740     Interpreter interpreter, Object[] args, CallStack callstack,
741     SimpleNode callerInfo
742   )
743         throws UtilEvalError, EvalError, ReflectError, InvocationTargetException
744     {
745         String methodName = Name.suffix(value, 1);
746     BshClassManager bcm = callstack.top().getClassManager();
747 
748     // Optimization - If classOfStaticMethod is set then we have already 
749     // been here and determined that this is a static method invocation.
750     // Note: maybe factor this out with path below... clean up.
751         if ( classOfStaticMethod != null )
752     {
753       return Reflect.invokeStaticMethod( 
754         bcm, classOfStaticMethod, methodName, args );
755     }
756 
757     if ( !Name.isCompound(value) )
758       return invokeLocalMethod( 
759         interpreter, args, callstack, callerInfo );
760 
761     // Note: if we want methods declared inside blocks to be accessible via
762     // this.methodname() inside the block we could handle it here as a
763     // special case.  See also resolveThisFieldReference() special handling
764     // for BlockNameSpace case.  They currently work via the direct name
765     // e.g. methodName().
766 
767         // Find target object or class identifier
768         Name targetName = namespace.getNameResolver( Name.prefix(value));
769 
770         Object obj = targetName.toObject( callstack, interpreter );
771 
772     if ( obj == Primitive.VOID ) 
773       throw new UtilEvalError( "Attempt to resolve method: "+methodName
774           +"() on undefined variable or class name: "+targetName);
775 
776         // if we've got an object, resolve the method
777         if ( !(obj instanceof ClassIdentifier) ) {
778 
779             if (obj instanceof Primitive) {
780 
781                 if (obj == Primitive.NULL)
782                     throw new UtilTargetError( new NullPointerException( 
783             "Null Pointer in Method Invocation" ) );
784 
785                 // some other primitive
786                 // should avoid calling methods on primitive, as we do
787                 // in Name (can't treat primitive like an object message)
788                 // but the hole is useful right now.
789         if ( Interpreter.DEBUG )
790                   interpreter.debug(
791           "Attempt to access method on primitive..." 
792           + " allowing bsh.Primitive to peek through for debugging");
793             }
794 
795             // found an object and it's not an undefined variable
796             return Reflect.invokeObjectMethod(
797         obj, methodName, args, interpreter, callstack, callerInfo );
798         }
799 
800     // It's a class
801 
802         // try static method
803         if ( Interpreter.DEBUG ) 
804           Interpreter.debug("invokeMethod: trying static - " + targetName);
805 
806         Class clas = ((ClassIdentifier)obj).getTargetClass();
807 
808     // cache the fact that this is a static method invocation on this class
809     classOfStaticMethod = clas;
810     
811         if ( clas != null )
812       return Reflect.invokeStaticMethod( bcm, clas, methodName, args );
813 
814         // return null; ???
815     throw new UtilEvalError("invokeMethod: unknown target: " + targetName);
816     }
817 
818   /**
819     Invoke a locally declared method or a bsh command.
820     If the method is not already declared in the namespace then try
821     to load it as a resource from the /bsh/commands path.
822   */
823     private Object invokeLocalMethod( 
824     Interpreter interpreter, Object[] args, CallStack callstack,
825     SimpleNode callerInfo
826   )
827         throws EvalError/*, ReflectError, InvocationTargetException*/
828     {
829         if ( Interpreter.DEBUG ) 
830           Interpreter.debug( "invokeLocalMethod: " + value );
831     if ( interpreter == null )
832       throw new InterpreterError(
833         "invokeLocalMethod: interpreter = null");
834 
835     String commandName = value;
836     Class [] argTypes = Reflect.getTypes( args );
837 
838         // Check for existing method
839         BshMethod meth = null;
840     try {
841       meth = namespace.getMethod( commandName, argTypes );
842     } catch ( UtilEvalError e ) {
843       throw e.toEvalError(
844         "Local method invocation", callerInfo, callstack );
845     }
846 
847     // If defined, invoke it
848         if ( meth != null )
849       return meth.invoke( args, interpreter, callstack, callerInfo );
850 
851     BshClassManager bcm = interpreter.getClassManager();
852 
853     Object commandObject;
854     try {
855       commandObject = namespace.getCommand( 
856         commandName, argTypes, interpreter );
857     } catch ( UtilEvalError e ) {
858       throw e.toEvalError("Error loading command: ", 
859         callerInfo, callstack );
860     }
861 
862     // should try to print usage here if nothing found
863     if ( commandObject == null )
864     {
865       // Look for a default invoke() handler method in the namespace
866       // Note: this code duplicates that in This.java... should it?
867       // Call on 'This' can never be a command
868       BshMethod invokeMethod = null;
869       try {
870         invokeMethod = namespace.getMethod( 
871           "invoke", new Class [] { null, null } );
872       } catch ( UtilEvalError e ) {
873         throw e.toEvalError(
874           "Local method invocation", callerInfo, callstack );
875       }
876 
877       if ( invokeMethod != null )
878         return invokeMethod.invoke( 
879           new Object [] { commandName, args }, 
880           interpreter, callstack, callerInfo );
881 
882             throw new EvalError( "Command not found: " 
883         +StringUtil.methodString( commandName, argTypes ), 
884         callerInfo, callstack );
885     }
886 
887     if ( commandObject instanceof BshMethod )
888       return ((BshMethod)commandObject).invoke( 
889         args, interpreter, callstack, callerInfo );
890 
891     if ( commandObject instanceof Class )
892       try {
893         return Reflect.invokeCompiledCommand( 
894           ((Class)commandObject), args, interpreter, callstack );
895       } catch ( UtilEvalError e ) {
896         throw e.toEvalError("Error invoking compiled command: ",
897         callerInfo, callstack );
898       }
899 
900     throw new InterpreterError("invalid command type");
901     }
902 
903 /*
904   private String getHelp( String name )
905     throws UtilEvalError
906   {
907     try {
908       // should check for null namespace here
909       return get( "bsh.help."+name, null/interpreter/ );
910     } catch ( Exception e ) {
911       return "usage: "+name;
912     }
913   }
914 
915   private String getHelp( Class commandClass )
916     throws UtilEvalError
917   {
918         try {
919             return (String)Reflect.invokeStaticMethod(
920         null/bcm/, commandClass, "usage", null );
921         } catch( Exception e )
922       return "usage: "+name;
923     }
924   }
925 */
926 
927   // Static methods that operate on compound ('.' separated) names
928   // I guess we could move these to StringUtil someday
929 
930   public static boolean isCompound(String value)
931   {
932     return value.indexOf('.') != -1 ;
933     //return countParts(value) > 1;
934   }
935 
936   static int countParts(String value)
937   {
938     if(value == null)
939       return 0;
940 
941     int count = 0;
942     int index = -1;
943     while((index = value.indexOf('.', index + 1)) != -1)
944       count++;
945     return count + 1;
946   }
947 
948   static String prefix(String value)
949   {
950     if(!isCompound(value))
951       return null;
952 
953     return prefix(value, countParts(value) - 1);
954   }
955 
956   static String prefix(String value, int parts)
957   {
958     if (parts < 1 )
959       return null;
960 
961     int count = 0;
962     int index = -1;
963 
964     while( ((index = value.indexOf('.', index + 1)) != -1) 
965       && (++count < parts) )
966     { ; }
967 
968     return (index == -1) ? value : value.substring(0, index);
969   }
970 
971   static String suffix(String name)
972   {
973     if(!isCompound(name))
974       return null;
975 
976     return suffix(name, countParts(name) - 1);
977   }
978 
979   public static String suffix(String value, int parts)
980   {
981     if (parts < 1)
982       return null;
983 
984     int count = 0;
985     int index = value.length() + 1;
986 
987     while ( ((index = value.lastIndexOf('.', index - 1)) != -1) 
988       && (++count < parts) );
989 
990     return (index == -1) ? value : value.substring(index + 1);
991   }
992 
993   // end compound name routines
994 
995 
996   public String toString() { return value; }
997 
998 }
999