/jEdit/tags/jedit-4-0-pre5/bsh/Name.java
Java | 879 lines | 428 code | 116 blank | 335 comment | 122 complexity | 0886a33fec5f8a2682bb72b1115282ba MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
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
35package bsh;
36
37import java.lang.reflect.Array;
38import java.util.Hashtable;
39import java.io.*;
40import 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 localy declared bsh method.
51 <p>
52
53 *** implementing ***
54 Name objects are not to be constructed arbitrarily, but are to be
55 factoried by NameSpace.getNameResolver, which caches them subject to
56 a namespace change. This means that we can cache information about various
57 types of resolution here.
58*/
59/*
60 <strong>Implementation notes</strong>
61 <pre>
62
63 *** implementing ***
64 Name objects are not to be constructed arbitrarily, but are to be
65 factoried by NameSpace.getNameResolver, which caches them subject to
66 a namespace change. This means that we can cache information about various
67 types of resolution here.
68 Note that we cannot simply cache any result, we must be smart about it.
69 For example, the value of a variable may change between calls. So we could
70 cache the knowledge that it is a variable, but must re-fetch the value
71 each time. We could even do cool optimizations such as knowing about
72 'final' variables ;)
73
74 Questions: how much will this actually buy us? The simple cases are not
75 expensive here, are they? How often are long chains of names evaluated?
76 *** implementing ***
77
78 Threads:
79 Thread safety: all of the work methods in this class must be synchronized
80 because they share the internal intermediate evaluation state.
81
82 Random note:
83 In some ways Name wants to be a private inner class of NameSpace...
84 However it is used elsewhere as an absraction for objects which haven't
85 been pinned down yet. So it is exposed.
86
87 Note on this.caller resolution:
88 Although references like these do work:
89
90 this.caller.caller.caller... // works
91
92 the equivalent using successive calls:
93
94 // does *not* work
95 for( caller=this.caller; caller != null; caller = caller.caller );
96
97 is prohibited by the restriction that you can only call .caller on a
98 literal this or caller reference.
99 The effect is that magic caller reference only works through the current
100 'this' reference.
101 The real explanation is that This referernces do not really know anything
102 about their depth on the call stack. It might even be hard to define
103 such a thing...
104
105 For those purposes we provide :
106
107 this.callstack
108
109 </pre>
110*/
111class Name implements java.io.Serializable
112{
113 // These do not change during evaluation
114 public NameSpace namespace;
115 String value = null;
116
117 // ---------------------------------------------------------
118 // The following instance variables mutate during evaluation and should
119 // be reset by the reset() method where necessary
120
121 // For evaluation
122 private String evalName; // text left to eval
123 private Object evalBaseObject; // base object for current eval
124
125 private int callstackDepth; // number of times eval hit 'this.caller'
126 /**
127 The last round consume the literal 'this' reference (not super,
128 global, or another This type var). We use this flag to support magic
129 variables that can only be referenced through 'this.xxx', e.g.
130 this.interpreter and this.caller;
131 */
132 private boolean literalThisReference;
133 /**
134 The last round consume the literal 'caller' reference (not super,
135 global, or another This type var). This is used to limit references
136 to .caller to only after a literal 'this' or compound '.caller'.
137 */
138 private boolean literalCallerReference;
139
140 //
141 // End mutable instance variables.
142 // ---------------------------------------------------------
143
144
145 private void reset() {
146 evalName = value;
147 evalBaseObject = null;
148 callstackDepth = 0;
149 literalThisReference=false;
150 literalCallerReference=false;
151 }
152
153 /**
154 This constructor should *not* be used in general.
155 Use NameSpace getNameResolver() which supports caching.
156 I wish I could make this "friendly" to just that class.
157 @see NameSpace getNameResolver().
158 */
159 public Name(NameSpace namespace, String s)
160 {
161 this.namespace = namespace;
162 value = s;
163 }
164
165 /**
166 Resolve possibly complex name to an object value.
167
168 Throws EvalError on various failures.
169 A null object value is indicated by a Primitive.NULL.
170 A return type of Primitive.VOID comes from attempting to access
171 an undefined variable.
172
173 Some cases:
174 myVariable
175 myVariable.foo
176 myVariable.foo.bar
177 java.awt.GridBagConstraints.BOTH
178 my.package.stuff.MyClass.someField.someField...
179
180 Interpreter reference is necessary to allow resolution of
181 "this.interpreter" magic field.
182 CallStack reference is necessary to allow resolution of
183 "this.caller" magic field.
184 "this.callstack" magic field.
185 */
186 public Object toObject( CallStack callstack, Interpreter interpreter )
187 throws EvalError
188 {
189 return toObject( callstack, interpreter, false );
190 }
191
192 /**
193 @see toObject()
194 @param forceClass if true then resolution will only produce a class.
195 This is necessary to disambiguate in cases where the grammar knows
196 that we want a class; where in general the var path may be taken.
197 */
198 synchronized public Object toObject(
199 CallStack callstack, Interpreter interpreter, boolean forceClass )
200 throws EvalError
201 {
202 reset();
203
204 Object obj = null;
205 while( evalName != null )
206 obj = consumeNextObjectField( callstack, interpreter, forceClass );
207
208 if ( obj == null )
209 throw new InterpreterError("null value in toObject()");
210
211 return obj;
212 }
213
214 /**
215 Get next prefixed object field component
216 */
217 private Object consumeNextObjectField(
218 CallStack callstack, Interpreter interpreter, boolean forceClass )
219 throws EvalError
220 {
221 /*
222 Is it a simple variable name?
223 Doing this first gives the correct Java precedence for vars
224 vs. imported class names (at least in the simple case - see
225 tests/precedence1.bsh). It should also speed things up a bit.
226 */
227 if ( (evalBaseObject == null && !isCompound(evalName) )
228 && !forceClass )
229 {
230 Object obj = resolveThisFieldReference(
231 callstack, namespace, interpreter, evalName, false );
232
233 if ( obj != Primitive.VOID ) {
234 evalName = null; // finished
235 return evalBaseObject = obj; // convention
236 }
237 }
238
239 /*
240 Is it a bsh script variable reference?
241 If we're just starting the eval of name (no base object)
242 or we're evaluating relative to a This reference check.
243 */
244 if ( ( evalBaseObject == null || evalBaseObject instanceof This )
245 && !forceClass )
246 {
247 String varName = prefix(evalName, 1);
248 Interpreter.debug("trying to resolve variable: " + varName);
249 Object obj;
250 if ( evalBaseObject == null ) {
251 obj = resolveThisFieldReference(
252 callstack, namespace, interpreter, varName, false );
253 } else {
254 // null callstack, cannot be caller reference
255 obj = resolveThisFieldReference(
256 callstack, ((This)evalBaseObject).namespace,
257 interpreter, varName, true );
258 }
259
260 if ( obj != Primitive.VOID )
261 {
262 // Resolved the variable
263 Interpreter.debug( "resolved variable: " + varName +
264 " in namespace: "+namespace);
265 evalName = suffix(evalName);
266 return evalBaseObject = obj;
267 }
268 }
269
270 /*
271 Is it a class name?
272 If we're just starting eval of name try to make it, else fail.
273 */
274 if ( evalBaseObject == null ) {
275 Interpreter.debug( "trying class: " + evalName);
276
277 /*
278 Keep adding parts until we have a class
279 */
280 Class clas = null;
281 int i = 1;
282 for(; i <= countParts(evalName); i++)
283 if ( (clas = namespace.getClass(prefix(evalName, i))) != null )
284 break;
285
286 if( clas != null ) {
287 evalName = suffix(evalName, countParts(evalName) - i);
288 return ( evalBaseObject = new ClassIdentifier(clas) );
289 }
290 // not a class (or variable per above)
291 Interpreter.debug( "not a class, trying var prefix "+evalName );
292 }
293
294
295 /*
296 If we didn't find a class or variable name (or prefix) above
297 there are two possibilities:
298
299 - If we are a simple name then we can pass as a void variable
300 reference.
301 - If we are compound then we must fail at this point.
302 */
303 if ( evalBaseObject == null ) {
304 if( !isCompound(evalName) ) {
305 evalName = null; // finished
306 return evalBaseObject = Primitive.VOID; // convention
307 } else
308 throw new EvalError(
309 "Class or variable not found:" + evalName);
310 }
311
312 /*
313 --------------------------------------------------------
314 After this point we're definitely evaluating relative to
315 a base object.
316 --------------------------------------------------------
317 */
318
319 /*
320 Do some basic validity checks.
321 */
322
323 if(evalBaseObject == Primitive.NULL) // previous round produced null
324 throw new TargetError( "Null Pointer while evaluating: "
325 +value, new NullPointerException() );
326
327 if(evalBaseObject == Primitive.VOID) // previous round produced void
328 throw new EvalError(
329 "Undefined variable or class name while evaluating: "+value);
330
331 if(evalBaseObject instanceof Primitive)
332 throw new EvalError("Can't treat primitive like an object. "+
333 "Error while evaluating: "+value);
334
335 /*
336 Resolve relative to a class type
337 static field, inner class, ?
338 */
339 if ( evalBaseObject instanceof ClassIdentifier )
340 {
341 Class clas = ((ClassIdentifier)evalBaseObject).getTargetClass();
342 String field = prefix(evalName, 1);
343
344 Object obj = null;
345 // static field?
346 try {
347//System.err.println("Name call to getStaticField, class: "
348 //+clas+", field:"+field);
349 obj = Reflect.getStaticField(clas, field);
350 } catch(ReflectError e) { }
351
352 // inner class?
353 if ( obj == null ) {
354 String iclass = clas.getName()+"$"+field;
355 Class c = namespace.getClass( iclass );
356 if ( c != null )
357 obj = new ClassIdentifier(c);
358 }
359
360 if ( obj == null )
361 throw new EvalError(
362 "No static field or inner class: " + field + " of " + clas);
363
364 evalName = suffix(evalName);
365 return (evalBaseObject = obj);
366 }
367
368 /*
369 If we've fallen through here we are no longer resolving to
370 a class type.
371 */
372 if ( forceClass )
373 throw new EvalError( value +" does not resolve to a class name." );
374
375 /*
376 Some kind of field access?
377 */
378
379 String field = prefix(evalName, 1);
380
381 /* length access on array? */
382 if(field.equals("length") && evalBaseObject.getClass().isArray())
383 {
384 Object obj = new Primitive(Array.getLength(evalBaseObject));
385 evalName = suffix(evalName);
386 return (evalBaseObject = obj);
387 }
388
389 /* check for field on object */
390 // Note: could eliminate throwing the exception somehow
391 try
392 {
393 Object obj = Reflect.getObjectField(evalBaseObject, field);
394 evalName = suffix(evalName);
395 return (evalBaseObject = obj);
396 }
397 catch(ReflectError e) { /* not a field */ }
398
399 // if we get here we have failed
400 throw new EvalError(
401 "Cannot access field: " + field + ", on object: " + evalBaseObject);
402 }
403
404 /**
405 Resolve a variable relative to a This reference.
406
407 This is the general variable resolution method, accomodating special
408 fields from the This context. Together the namespace and interpreter
409 comprise the This context. The callstack, if available allows for the
410 this.caller construct.
411 Optionally interpret special "magic" field names: e.g. interpreter.
412
413 @param callstack may be null, but this is only legitimate in special
414 cases where we are sure resolution will not involve this.caller.
415
416 @param namespace the namespace of the this reference (should be the
417 same as the top of the stack?
418 */
419 Object resolveThisFieldReference(
420 CallStack callstack, NameSpace thisNamespace, Interpreter interpreter,
421 String varName, boolean specialFieldsVisible )
422 throws EvalError
423 {
424 Object obj = null;
425 // preserve the state of the last round flags until the end
426 boolean
427 wasThis = false,
428 wasCaller = false;
429
430 if ( varName.equals("this") ) {
431 // Hack! If the special fields are visible turn of further .this
432 // prevent user from skipping to things like super.this.caller
433 if ( specialFieldsVisible )
434 throw new EvalError("Redundant to call .this on This type");
435 obj = thisNamespace.getThis( interpreter );
436 wasThis = true;
437 }
438
439 if ( obj == null ) {
440 if ( varName.equals("super") )
441 obj = thisNamespace.getSuper().getThis( interpreter );
442 else if ( varName.equals("global") )
443 obj = thisNamespace.getGlobal().getThis( interpreter );
444 }
445
446 if ( obj == null && specialFieldsVisible ) {
447 if (varName.equals("namespace"))
448 obj = thisNamespace;
449 else if (varName.equals("variables"))
450 obj = thisNamespace.getVariableNames();
451 else if (varName.equals("methods"))
452 obj = thisNamespace.getMethodNames();
453 else if ( varName.equals("interpreter") )
454 if ( literalThisReference )
455 obj = interpreter;
456 else
457 throw new EvalError(
458 "Can only call .interpreter on literal 'this'");
459 }
460
461 if ( obj == null && specialFieldsVisible && varName.equals("caller") )
462 {
463 if ( literalThisReference || literalCallerReference )
464 {
465 // get the previous context (see notes for this class)
466 if ( callstack == null )
467 throw new InterpreterError("no callstack");
468 obj = callstack.get( ++callstackDepth ).getThis(
469 interpreter );
470 }
471 else
472 throw new EvalError(
473 "Can only call .caller on literal 'this' or literal '.caller'");
474
475 wasCaller = true;
476 }
477
478 if ( obj == null && specialFieldsVisible
479 && varName.equals("callstack") )
480 {
481 if ( literalThisReference )
482 {
483 // get the previous context (see notes for this class)
484 if ( callstack == null )
485 throw new InterpreterError("no callstack");
486 obj = callstack;
487 }
488 else
489 throw new EvalError(
490 "Can only call .callstack on literal 'this'");
491 }
492
493
494 if ( obj == null )
495 obj = thisNamespace.getVariable(varName);
496
497 literalThisReference = wasThis;
498 literalCallerReference = wasCaller;
499 return obj;
500 }
501
502 /**
503 Check the cache, else use toObject() to try to resolve to a class
504 identifier.
505
506 Throws EvalError on class not found...
507 */
508 synchronized public Class toClass() throws EvalError
509 {
510 reset();
511
512 /* Try straightforward class name first */
513 Class clas = namespace.getClass(evalName);
514
515 if ( clas == null ) {
516 /*
517 Try toObject() which knows how to work through inner classes
518 and see what we end up with
519 */
520 Object obj = null;
521 try {
522 // Null interpreter and callstack references.
523 // class only resolution should not require them.
524 obj = toObject( null, null, true );
525 } catch ( EvalError e ) { }; // couldn't resolve it
526
527 if ( obj instanceof ClassIdentifier )
528 clas = ((ClassIdentifier)obj).getTargetClass();
529 }
530
531 if( clas == null )
532 throw new EvalError(
533 "Class: " + value+ " not found in namespace");
534
535 return clas;
536 }
537
538 /*
539 */
540 synchronized public LHS toLHS(
541 CallStack callstack, Interpreter interpreter )
542 throws EvalError
543 {
544 reset();
545
546 //Interpreter.debug("Name toLHS: "+evalName+ " isCompound = "
547 //+isCompound(evalName));
548
549 // variable
550 if(!isCompound(evalName)) {
551 //Interpreter.debug("returning simple var LHS...");
552 return new LHS(namespace,evalName);
553 }
554
555 // field
556 Object obj = null;
557 try
558 {
559 while(isCompound(evalName))
560 obj = consumeNextObjectField( callstack, interpreter, false );
561 }
562 catch( EvalError e )
563 {
564 throw new EvalError("LHS evaluation: " + e);
565 }
566
567 if ( obj == null )
568 throw new InterpreterError("internal error 2893749283");
569
570 if(obj instanceof This)
571 {
572 Interpreter.debug("found This reference evaluating LHS");
573 return new LHS(((This)obj).namespace, evalName);
574 }
575
576 if(evalName != null)
577 {
578 try
579 {
580//System.err.println("Name getLHSObjectField call obj = "
581// +obj+", name="+evalName);
582
583 if ( obj instanceof ClassIdentifier )
584 {
585 Class clas = ((ClassIdentifier)obj).getTargetClass();
586 return Reflect.getLHSStaticField(clas, evalName);
587 } else
588 return Reflect.getLHSObjectField(obj, evalName);
589 } catch(ReflectError e)
590 {
591 throw new EvalError("Field access: "+e);
592 }
593 }
594
595 throw new InterpreterError("Internal error in lhs...");
596
597 /*
598 This appears to have been something very old and incorrect...
599 I don't think we need it anymore.
600
601 // We bit off our field in the very first bite
602 // have to back off and make a class out of the prefix
603 Interpreter.debug("very first field was it...");
604
605 Class clas = namespace.getClass(prefix(value));
606 if(clas == null)
607 throw new InterpreterError("internal error 238974983");
608
609 String field = suffix(value, 1);
610
611 try
612 {
613 return Reflect.getLHSStaticField(clas, field);
614 }
615 catch(ReflectError e)
616 {
617 Interpreter.debug("reflect error:" + e);
618 return null;
619 }
620 */
621
622 }
623
624 private BshMethod toLocalMethod( Object [] args )
625 {
626 Class [] sig = Reflect.getTypes( args );
627 return namespace.getMethod( value, sig );
628 }
629
630
631 /**
632 Invoke the method identified by name.
633
634 Name contains a wholely unqualfied messy name; resolve it to
635 ( object | static prefix ) + method name and invoke.
636
637 The interpreter is necessary to support 'this.interpreter' references
638 in the called code. (e.g. debug());
639
640 Some cases:
641
642 // dynamic
643 local();
644 myVariable.foo();
645 myVariable.bar.blah.foo();
646 // static
647 java.lang.Integer.getInteger("foo");
648
649 */
650 public Object invokeMethod(
651 Interpreter interpreter, Object[] args, CallStack callstack,
652 SimpleNode callerInfo
653 )
654 throws EvalError, ReflectError, InvocationTargetException
655 {
656 if ( !Name.isCompound(value) )
657 return invokeLocalMethod(interpreter, args, callstack, callerInfo);
658
659 // find target object
660 Name targetName = namespace.getNameResolver( Name.prefix(value));
661 String methodName = Name.suffix(value, 1);
662
663 Object obj = targetName.toObject( callstack, interpreter );
664
665 if ( obj == Primitive.VOID )
666 throw new EvalError( "Attempt to invoke method: "+methodName
667 +"() on undefined variable or class name: "+targetName);
668
669 // if we've got an object, invoke the method
670 if ( !(obj instanceof Name.ClassIdentifier) ) {
671
672 if (obj instanceof Primitive) {
673
674 if (obj == Primitive.NULL)
675 throw new TargetError( "Null Pointer in Method Invocation",
676 new NullPointerException() );
677
678 // some other primitive
679 // should avoid calling methods on primitive, as we do
680 // in Name (can't treat primitive like an object message)
681 // but the hole is useful right now.
682 interpreter.error("Attempt to access method on primitive..." +
683 " allowing bsh.Primitive to peek through for debugging");
684 }
685
686 // found an object and it's not an undefined variable
687 return Reflect.invokeObjectMethod(
688 interpreter, obj, methodName, args, callerInfo);
689 }
690
691 // try static method
692 Interpreter.debug("invokeMethod: trying static - " + targetName);
693
694 Class clas = ((Name.ClassIdentifier)obj).getTargetClass();
695 if (clas != null)
696 return Reflect.invokeStaticMethod(clas, methodName, args);
697
698 // return null; ???
699 throw new EvalError("unknown target: " + targetName);
700 }
701
702 /**
703 Invoke a locally declared method or a bsh command.
704 If the method is not already declared in the namespace then try
705 to load it as a resource from the /bsh/commands path.
706
707 Note: instead of invoking the method directly here we should probably
708 call invokeObjectMethod passing a This reference. That would have
709 the side effect of allowing a locally defined invoke() method to
710 handle undeclared method invocations just like in objects. Not sure
711 if this is desirable... It seems that if you invoke a method directly
712 in scope it should be there.
713
714 Keeping this code separate allows us to differentiate between methods
715 invoked directly in scope and those invoked through object references.
716 */
717 public Object invokeLocalMethod(
718 Interpreter interpreter, Object[] args, CallStack callstack,
719 SimpleNode callerInfo
720 )
721 throws EvalError, ReflectError, InvocationTargetException
722 {
723 Interpreter.debug("invoke local method: " + value);
724
725 // Check for locally declared method
726 BshMethod meth = toLocalMethod( args );
727 if ( meth != null )
728 return meth.invokeDeclaredMethod( args, interpreter, callstack, callerInfo );
729 else
730 Interpreter.debug("no locally declared method: " + value);
731
732 /*
733 Look for scripted command as resource
734 */
735 // Why not /bsh/commands here? Why relative to Interpreter?
736 String commandName = "commands/" + value + ".bsh";
737 InputStream in = Interpreter.class.getResourceAsStream(commandName);
738 if (in != null)
739 {
740 Interpreter.debug("loading resource: " + commandName);
741
742 if ( interpreter == null )
743 throw new InterpreterError("2234432 interpreter = null");
744
745 interpreter.eval(
746 new InputStreamReader(in), namespace, commandName);
747
748 // try again
749 meth = toLocalMethod( args );
750 if(meth != null)
751 return meth.invokeDeclaredMethod(
752 args, interpreter, callstack, callerInfo );
753 else
754 throw new EvalError("Loaded resource: " + commandName +
755 "had an error or did not contain the correct method");
756 }
757
758 // check for compiled bsh command class
759 commandName = "bsh.commands." + value;
760 // create class outside of any namespace
761 Class c = BshClassManager.classForName( commandName );
762 if(c == null)
763 throw new EvalError("Command not found: " + value);
764
765 // add interpereter and namespace to args list
766 Object[] invokeArgs = new Object[args.length + 2];
767 invokeArgs[0] = interpreter;
768 invokeArgs[1] = namespace;
769 System.arraycopy(args, 0, invokeArgs, 2, args.length);
770 try
771 {
772 return Reflect.invokeStaticMethod(c, "invoke", invokeArgs);
773 }
774 catch(ReflectError e)
775 {
776 Interpreter.debug("invoke command args error:" + e);
777 // bad args
778 }
779 // try to print help
780 try
781 {
782 String s = (String)Reflect.invokeStaticMethod(c, "usage", null);
783 interpreter.println(s);
784 return Primitive.VOID;
785 }
786 catch(ReflectError e)
787 {
788 Interpreter.debug("usage threw: " + e);
789 throw new EvalError("Wrong number or type of args for command");
790 }
791 }
792
793 // Static methods that operate on compound ('.' separated) names
794
795 static boolean isCompound(String value)
796 {
797 return countParts(value) > 1;
798 }
799
800 static int countParts(String value)
801 {
802 if(value == null)
803 return 0;
804
805 int count = 0;
806 int index = -1;
807 while((index = value.indexOf('.', index + 1)) != -1)
808 count++;
809 return count + 1;
810 }
811
812 static String prefix(String value)
813 {
814 if(!isCompound(value))
815 return null;
816
817 return prefix(value, countParts(value) - 1);
818 }
819
820 static String prefix(String value, int parts)
821 {
822 if(parts < 1)
823 return null;
824
825 int count = 0;
826 int index = -1;
827
828 while(((index = value.indexOf('.', index + 1)) != -1) && (++count < parts))
829 { ; }
830
831 return (index == -1) ? value : value.substring(0, index);
832 }
833
834 static String suffix(String name)
835 {
836 if(!isCompound(name))
837 return null;
838
839 return suffix(name, countParts(name) - 1);
840 }
841
842 public static String suffix(String value, int parts)
843 {
844 if(parts < 1)
845 return null;
846
847 int count = 0;
848 int index = value.length() + 1;
849
850 while(((index = value.lastIndexOf('.', index - 1)) != -1) && (++count < parts))
851 { ; }
852
853 return (index == -1) ? value : value.substring(index + 1);
854 }
855
856 // end compound name routines
857
858
859 public String toString() { return value; }
860
861 static class ClassIdentifier {
862 Class clas;
863
864 public ClassIdentifier( Class clas ) {
865 this.clas = clas;
866 }
867
868 public Class getTargetClass() {
869 return clas;
870 }
871
872 public String toString() {
873 return "Class Identifier: "+clas.getName();
874 }
875 }
876
877
878}
879