source: branches/1.1.x/src/org/armedbear/lisp/JavaObject.java

Last change on this file was 13608, checked in by Mark Evenson, 13 years ago

Fill in some missing docstrings from JAVA package.

Unfortunately, we have to use static initializers and retain the '###'
marker for documenting package variables as we don't seem to be able
to use the @DocString? annotation as the JVM reflection APIs only seem
to require that we know the Java class that a field is part of in
order to look up runtime annotations. If this understanding is
faulty, please let me know otherwise how to do it.

The :DOCUMENTATION option for the DEFGENERIC is apparently not
working.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 24.6 KB
Line 
1/*
2 * JavaObject.java
3 *
4 * Copyright (C) 2002-2005 Peter Graves
5 * $Id: JavaObject.java 13608 2011-09-30 14:18:04Z mevenson $
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 *
21 * As a special exception, the copyright holders of this library give you
22 * permission to link this library with independent modules to produce an
23 * executable, regardless of the license terms of these independent
24 * modules, and to copy and distribute the resulting executable under
25 * terms of your choice, provided that you also meet, for each linked
26 * independent module, the terms and conditions of the license of that
27 * module.  An independent module is a module which is not derived from
28 * or based on this library.  If you modify this library, you may extend
29 * this exception to your version of the library, but you are not
30 * obligated to do so.  If you do not wish to do so, delete this
31 * exception statement from your version.
32 */
33
34package org.armedbear.lisp;
35
36import static org.armedbear.lisp.Lisp.*;
37
38import java.lang.reflect.Array;
39import java.lang.reflect.Field;
40import java.math.BigInteger;
41import java.util.*;
42
43public final class JavaObject extends LispObject {
44    final Object obj;
45    private final Class<?> intendedClass;
46
47    public JavaObject(Object obj) {
48        this.obj = obj;
49        this.intendedClass =
50            obj != null ? Java.maybeBoxClass(obj.getClass()) : null;
51    }
52
53    public static final Symbol JAVA_CLASS_JCLASS = PACKAGE_JAVA.intern("JAVA-CLASS-JCLASS");
54    public static final Symbol JAVA_CLASS = PACKAGE_JAVA.intern("JAVA-CLASS");
55    public static final Symbol ENSURE_JAVA_CLASS = PACKAGE_JAVA.intern("ENSURE-JAVA-CLASS");
56
57    /**
58     * Constructs a Java Object with the given intended class, used to access
59     * the object reflectively. If the class represents a primitive type,
60     * the corresponding wrapper type is used instead.
61     * @throws ClassCastException if the object is not an instance of the
62     *                            intended class.
63     */
64    public JavaObject(Object obj, Class<?> intendedClass) {
65        if(obj != null && intendedClass == null) {
66            intendedClass = obj.getClass();
67        }
68        if(intendedClass != null) {
69            intendedClass = Java.maybeBoxClass(intendedClass);
70            if(!intendedClass.isInstance(obj)) {
71                if (intendedClass.equals(java.lang.Byte.class)
72                    && obj instanceof java.lang.Number) {
73                    // Maps any number to two's complement 8bit byte representation
74                    // ??? Is this a reasonable thing?
75                    this.obj = ((java.lang.Number)obj).byteValue();
76                    this.intendedClass = intendedClass;
77                    return;
78                }
79                throw new ClassCastException(obj + " can not be cast to " + intendedClass);
80            }
81        }
82        this.obj = obj;
83        this.intendedClass = intendedClass;
84    }
85
86    @Override
87    public LispObject typeOf()
88    {
89        return Symbol.JAVA_OBJECT;
90    }
91
92    @Override
93    public LispObject classOf()
94    {
95        if(obj == null) {
96                return BuiltInClass.JAVA_OBJECT;
97        } else {
98            return ENSURE_JAVA_CLASS.execute(new JavaObject(obj.getClass()));
99        }
100    }
101
102    @Override
103    public LispObject typep(LispObject type) {
104        if (type == Symbol.JAVA_OBJECT)
105            return T;
106        if (type == BuiltInClass.JAVA_OBJECT)
107            return T;
108        LispObject cls = NIL;
109        if(type instanceof Symbol) {
110            cls = LispClass.findClass(type, false);
111        }
112        if(cls == NIL) {
113            cls = type;
114        }
115        if(cls.typep(LispClass.findClass(JAVA_CLASS, false)) != NIL) {
116            if(obj != null) {
117                Class c = (Class) JAVA_CLASS_JCLASS.execute(cls).javaInstance();
118                return c.isAssignableFrom(obj.getClass()) ? T : NIL;
119            } else {
120                return T;
121            }
122        } else if(cls == BuiltInClass.SEQUENCE) {
123            //This information is replicated here from java.lisp; it is a very
124            //specific case, not worth implementing CPL traversal in typep
125            if(java.util.List.class.isInstance(obj) ||
126               java.util.Set.class.isInstance(obj)) {
127                return T;
128            }
129        }
130        return super.typep(type);
131    }
132
133    @Override
134    public LispObject STRING()
135    {
136        return new SimpleString(obj != null? obj.toString(): "null");
137    }
138
139    public final Object getObject()
140    {
141        return obj;
142    }
143
144    /** Encapsulates obj, if required.
145     * If obj is a {@link  LispObject}, it's returned as-is.
146     *
147     * @param obj Any java object
148     * @return obj or a new JavaObject encapsulating obj
149     */
150    public final static LispObject getInstance(Object obj) {
151        if (obj == null)
152            return new JavaObject(null);
153       
154        if (obj instanceof LispObject)
155            return (LispObject)obj;
156
157        return new JavaObject(obj);
158    }
159
160    /** Encapsulates obj, if required.
161     * If obj is a {@link LispObject}, it's returned as-is.
162     * If not, a java object with the specified intended class is returned.
163     *
164     * @param obj Any java object
165     * @param intendedClass the class that shall be used to access obj
166     * @return obj or a new JavaObject encapsulating obj
167     */
168    public final static LispObject getInstance(Object obj, Class<?> intendedClass) {
169        if (obj == null)
170            return new JavaObject(null);
171       
172        if (obj instanceof LispObject)
173            return (LispObject)obj;
174
175        return new JavaObject(obj, intendedClass);
176    }
177
178    /** Encapsulates obj, if required.
179     * If obj is a {@link LispObject}, it's returned as-is.
180     * If obj is of a type which can be mapped to a lisp type,
181     * an object of the mapped type is returned, if translated is true.
182     *
183     * @param obj
184     * @param translated
185     * @return a LispObject representing or encapsulating obj
186     */
187    public final static LispObject getInstance(Object obj, boolean translated) {
188        return getInstance(obj, translated, obj != null ? obj.getClass() : null);
189    }
190
191
192
193    /** Encapsulates obj, if required.
194     * If obj is a {@link LispObject}, it's returned as-is.
195     * If obj is of a type which can be mapped to a lisp type,
196     * an object of the mapped type is returned, if translated is true.
197     *
198     * @param obj
199     * @param translated
200     * @param intendedClass the class that shall be used to reflectively
201     *                      access obj; it is an error for obj not to be
202     *                      an instance of this class. This parameter is ignored
203     *                      if translated == true and the object can be
204     *                      converted to a Lisp object.
205     * @return a LispObject representing or encapsulating obj
206     */
207    public final static LispObject getInstance(Object obj, boolean translated, Class<?> intendedClass) {
208        if (! translated)
209            return getInstance(obj, intendedClass);
210
211        if (obj == null) return NIL;
212
213        if (obj instanceof LispObject)
214            return (LispObject)obj;
215
216        if (obj instanceof String)
217            return new SimpleString((String)obj);
218
219        if (obj instanceof Number) {
220            // Number types ordered according to decreasing
221            // estimated chances of occurrance
222
223            if (obj instanceof Integer)
224                return Fixnum.getInstance(((Integer)obj).intValue());
225
226            if (obj instanceof Float)
227                return new SingleFloat((Float)obj);
228
229            if (obj instanceof Double)
230                return new DoubleFloat((Double)obj);
231
232            if (obj instanceof Long)
233                return LispInteger.getInstance(((Long)obj).longValue());
234
235            if (obj instanceof BigInteger)
236                return Bignum.getInstance((BigInteger)obj);
237
238            if (obj instanceof Short)
239                return Fixnum.getInstance(((Short)obj).shortValue());
240
241            if (obj instanceof Byte)
242                return Fixnum.getInstance(((Byte)obj).byteValue());
243            // We don't handle BigDecimal: it doesn't map to a Lisp type
244        }
245
246        if (obj instanceof Boolean)
247            return ((Boolean)obj).booleanValue() ? T : NIL;
248
249        if (obj instanceof Character)
250            return LispCharacter.getInstance((Character)obj);
251
252        if (obj instanceof Object[]) {
253            Object[] array = (Object[]) obj;
254            SimpleVector v = new SimpleVector(array.length);
255            for (int i = array.length; i-- > 0;)
256                v.aset(i, JavaObject.getInstance(array[i], translated));
257            return v;
258        }
259        // TODO
260        // We might want to handle:
261        //  - streams
262        //  - others?
263        return new JavaObject(obj, intendedClass);
264    }
265
266    @Override
267    public Object javaInstance() {
268        return obj;
269    }
270
271    @Override
272    public Object javaInstance(Class<?> c) {
273        if(obj == null) {
274            if(c.isPrimitive()) {
275                throw new NullPointerException("Cannot assign null to " + c);
276            }
277            return obj;
278        } else {
279            c = Java.maybeBoxClass(c);
280            if (c.isAssignableFrom(intendedClass) || c.isInstance(obj)) {
281              // XXX In the case that c.isInstance(obj) should we then
282              // "fix" the intendedClass field with the (presumably)
283              // narrower type of 'obj'?
284
285              // ME 20100323: I decided not to because a) we don't
286              // know the "proper" class to narrow to (i.e. maybe
287              // there's something "narrower" and b) I'm not sure how
288              // primitive types relate to their boxed
289              // representations. 
290                return obj;
291            } else {
292                return error(new TypeError(intendedClass.getName() + " is not assignable to " + c.getName()));
293            }
294        }
295    }
296
297    /** Returns the encapsulated Java object for
298     * interoperability with wait, notify, synchronized, etc.
299     *
300     * @return The encapsulated object
301     */
302    @Override
303    public Object lockableInstance() {
304        return obj;
305    }
306
307    public Class<?> getIntendedClass() {
308        return intendedClass;
309    }
310
311    public static final Object getObject(LispObject o)
312
313    {
314        if (o instanceof JavaObject)
315                return ((JavaObject)o).obj;       
316        return             // Not reached.
317        type_error(o, Symbol.JAVA_OBJECT);       
318    }
319
320    @Override
321    public final boolean equal(LispObject other)
322    {
323        if (this == other)
324            return true;
325        if (other instanceof JavaObject)
326            return (obj == ((JavaObject)other).obj);
327        return false;
328    }
329
330    @Override
331    public final boolean equalp(LispObject other)
332    {
333        return equal(other);
334    }
335
336    @Override
337    public int sxhash()
338    {
339        return obj == null ? 0 : (obj.hashCode() & 0x7ffffff);
340    }
341
342    public static LispObject JAVA_OBJECT_TO_STRING_LENGTH
343        = LispInteger.getInstance(32);
344
345    public static final Symbol _JAVA_OBJECT_TO_STRING_LENGTH
346        = exportSpecial("*JAVA-OBJECT-TO-STRING-LENGTH*", 
347                        PACKAGE_JAVA, JAVA_OBJECT_TO_STRING_LENGTH);
348
349    static {
350        String doc = "Length to truncate toString() PRINT-OBJECT output for an otherwise "
351                  +  "unspecialized JAVA-OBJECT.  Can be set to NIL to indicate no limit.";
352        _JAVA_OBJECT_TO_STRING_LENGTH
353            .setDocumentation(Symbol.VARIABLE, new SimpleString(doc));
354    }
355
356    @Override
357    public String printObject()
358    {
359        if (obj instanceof ControlTransfer)
360            return obj.toString();
361        final String s;
362        if(obj != null) {
363            Class<?> c = obj.getClass();
364            StringBuilder sb
365                = new StringBuilder(c.isArray() ? "jarray" : c.getName());
366            sb.append(' ');
367            String ts = obj.toString();
368            int length = -1;
369            LispObject stringLength = _JAVA_OBJECT_TO_STRING_LENGTH.symbolValueNoThrow();
370            if (stringLength instanceof Fixnum) {
371                length = Fixnum.getValue(stringLength);
372            }
373            if (length < 0) {
374                sb.append(ts);
375            }else if (ts.length() > length) { 
376                // use '....' to not confuse user with PPRINT conventions
377                sb.append(ts.substring(0, length)).append("...."); 
378            } else {
379                sb.append(ts);
380            }
381            s = sb.toString();
382        } else {
383            s = "null";
384        }
385        return unreadableString(s);
386    }
387
388    @Override
389    public LispObject getDescription() {
390        return new SimpleString(describeJavaObject(this));
391    }
392
393    @Override
394    public LispObject getParts() {
395        if(obj != null) {
396            LispObject parts = NIL;
397            parts = parts.push(new Cons("Java class",
398                                        new JavaObject(obj.getClass())));
399            if (intendedClass != null) {
400                parts = parts.push(new Cons("intendedClass", new SimpleString(intendedClass.getCanonicalName())));
401            }
402            if (obj.getClass().isArray()) {
403                int length = Array.getLength(obj);
404                for (int i = 0; i < length; i++) {
405                    parts = parts
406                        .push(new Cons(new SimpleString(i), 
407                                       JavaObject.getInstance(Array.get(obj, i))));
408                }
409            } else {
410                parts = Symbol.NCONC.execute(parts, getInspectedFields());
411            }
412            if (obj instanceof java.lang.Class) {
413                Class o = (java.lang.Class)obj;
414                try {
415                    Class[] classes = o.getClasses();
416                    LispObject classesList = NIL;
417                    for (int i = 0; i < classes.length; i++) {
418                        classesList = classesList.push(JavaObject.getInstance(classes[i]));
419                    }
420                    if (!classesList.equals(NIL)) {
421                        parts = parts
422                            .push(new Cons("Member classes", classesList.nreverse()));
423                    }
424                } catch (SecurityException e) {
425                    Debug.trace(e);
426                }
427                Class[] interfaces = o.getInterfaces();
428                LispObject interfacesList = NIL;
429                for (int i = 0; i < interfaces.length; i++) {
430                    interfacesList = interfacesList.push(JavaObject.getInstance(interfaces[i]));
431                }
432                if (!interfacesList.equals(NIL)) {
433                    parts = parts
434                        .push(new Cons("Interfaces", interfacesList.nreverse()));
435                }
436                LispObject superclassList = NIL;
437                Class superclass = o.getSuperclass();
438                while (superclass != null) {
439                    superclassList = superclassList.push(JavaObject.getInstance(superclass));
440                    superclass = superclass.getSuperclass();
441                }
442                if (!superclassList.equals(NIL)) {
443                    parts = parts
444                        .push(new Cons("Superclasses", superclassList.nreverse()));
445                }
446            }
447            return parts.nreverse();
448        } else {
449            return NIL;
450        }
451    }
452
453    private LispObject getInspectedFields()
454        {
455        final LispObject[] acc = new LispObject[] { NIL };
456        doClassHierarchy(obj.getClass(), new Function() {
457                @Override
458                public LispObject execute(LispObject arg)
459                    {
460                    //No possibility of type error - we're mapping this function
461                    //over a list of classes
462                    Class<?> c = (Class) arg.javaInstance();
463                    for(Field f : c.getDeclaredFields()) {
464                        LispObject value = NIL;
465                        try {
466                            if(!f.isAccessible()) {
467                                f.setAccessible(true);
468                            }
469                            value = JavaObject.getInstance(f.get(obj));
470                        } catch(Exception e) {}
471                        acc[0] = acc[0].push(new Cons(f.getName(), value));
472                    }
473                    return acc[0];
474                }
475            });
476        return acc[0].nreverse();
477    }
478
479    /**
480     * Executes a function repeatedly over the minimal subtree of the
481     * Java class hierarchy which contains every class in <classes>.
482     */
483    private static void doClassHierarchy(Collection<Class<?>> classes,
484                                         LispObject callback,
485                                         Set<Class<?>> visited)
486        {
487        Collection<Class<?>> newClasses = new LinkedList<Class<?>>();
488        for(Class<?> clss : classes) {
489            if(clss == null) {
490                continue;
491            }
492            if(!visited.contains(clss)) {
493                callback.execute(JavaObject.getInstance(clss, true));
494                visited.add(clss);
495            }
496            if(!visited.contains(clss.getSuperclass())) {
497                newClasses.add(clss.getSuperclass());
498            }
499            for(Class<?> iface : clss.getInterfaces()) {
500                if (!visited.contains(iface)) {
501                    newClasses.add(iface);
502                }
503            }
504        }
505        if(!newClasses.isEmpty()) {
506            doClassHierarchy(newClasses, callback, visited);
507        }
508    }
509
510    /**
511     * Executes a function recursively over <clss> and its superclasses and
512     * interfaces.
513     */
514    public static void doClassHierarchy(Class<?> clss, LispObject callback)
515        {
516        if (clss != null) {
517            Set<Class<?>> visited = new HashSet<Class<?>>();
518            Collection<Class<?>> classes = new ArrayList<Class<?>>(1);
519            classes.add(clss);
520            doClassHierarchy(classes, callback, visited);
521        }
522    }
523
524    public static LispObject mapcarClassHierarchy(Class<?> clss,
525                                                  final LispObject fn)
526    {
527        final LispObject[] acc = new LispObject[] { NIL };
528        doClassHierarchy(clss, new Function() {
529                @Override
530                public LispObject execute(LispObject arg)
531                    {
532                    acc[0] = acc[0].push(fn.execute(arg));
533                    return acc[0];
534                }
535            });
536        return acc[0].nreverse();
537    }
538
539    public static String describeJavaObject(final JavaObject javaObject)
540        {
541        final Object obj = javaObject.getObject();
542        final StringBuilder sb =
543            new StringBuilder(javaObject.princToString());
544        sb.append(" is an object of type ");
545        sb.append(Symbol.JAVA_OBJECT.princToString());
546        sb.append(".");
547        sb.append(System.getProperty("line.separator"));
548        sb.append("The wrapped Java object is ");
549        if (obj == null) {
550            sb.append("null.");
551        } else {
552            sb.append("an ");
553            final Class c = obj.getClass();
554            String className = c.getName();
555            if (c.isArray()) {
556                sb.append("array of ");
557                if (className.startsWith("[L") && className.endsWith(";")) {
558                    className = className.substring(1, className.length() - 1);
559                    sb.append(className);
560                    sb.append(" objects");
561                } else if (className.startsWith("[") && className.length() > 1) {
562                    char descriptor = className.charAt(1);
563                    final String type;
564                    switch (descriptor) {
565                    case 'B': type = "bytes"; break;
566                    case 'C': type = "chars"; break;
567                    case 'D': type = "doubles"; break;
568                    case 'F': type = "floats"; break;
569                    case 'I': type = "ints"; break;
570                    case 'J': type = "longs"; break;
571                    case 'S': type = "shorts"; break;
572                    case 'Z': type = "booleans"; break;
573                    default:
574                        type = "unknown type";
575                    }
576                    sb.append(type);
577                }
578                sb.append(" with ");
579                final int length = java.lang.reflect.Array.getLength(obj);
580                sb.append(length);
581                sb.append(" element");
582                if (length != 1)
583                    sb.append('s');
584                sb.append('.');
585            } else {
586                sb.append("instance of ");
587                sb.append(className);
588                sb.append(':');
589                sb.append(System.getProperty("line.separator"));
590                sb.append("  \"");
591                sb.append(obj.toString());
592                sb.append('"');
593            }
594        }
595        return sb.toString();
596    }
597
598    // ### describe-java-object
599    private static final Primitive DESCRIBE_JAVA_OBJECT =
600        new Primitive("describe-java-object", PACKAGE_JAVA, true)
601    {
602        @Override
603        public LispObject execute(LispObject first, LispObject second)
604
605        {
606            if (!(first instanceof JavaObject))
607                return type_error(first, Symbol.JAVA_OBJECT);
608            final Stream stream = checkStream(second);
609            final JavaObject javaObject = (JavaObject) first;
610            stream._writeString(describeJavaObject(javaObject));
611            return LispThread.currentThread().nothing();
612        }
613    };
614
615    //JAVA-CLASS support
616
617    //There is no point for this Map to be weak since values keep a reference to the corresponding
618    //key (the Java class). This should not be a problem since Java classes are limited in number -
619    //if they grew indefinitely, the JVM itself would crash.
620    private static final Map<Class<?>, LispObject> javaClassMap = new HashMap<Class<?>, LispObject>();
621
622    public static LispObject registerJavaClass(Class<?> javaClass, LispObject classMetaObject) {
623        synchronized (javaClassMap) {
624            javaClassMap.put(javaClass, classMetaObject);
625            return classMetaObject;
626        }
627    }
628
629    public static LispObject findJavaClass(Class<?> javaClass) {
630        synchronized (javaClassMap) {
631            LispObject c = javaClassMap.get(javaClass);
632            if (c != null) {
633                return c;
634            } else {
635                return NIL;
636            }
637        }
638    }
639
640    private static final Primitive _FIND_JAVA_CLASS = new Primitive("%find-java-class", PACKAGE_JAVA, false, "class-name-or-class") {
641            public LispObject execute(LispObject arg) {
642                try {
643                    if(arg instanceof AbstractString) {
644                        return findJavaClass(Class.forName((String) arg.getStringValue()));
645                    } else {
646                        return findJavaClass((Class<?>) arg.javaInstance());
647                    }
648                } catch (ClassNotFoundException e) {
649                    return error(new LispError("Cannot find Java class " + arg.getStringValue()));
650                }
651            }
652           
653        };
654
655    private static final Primitive _REGISTER_JAVA_CLASS = new Primitive("%register-java-class", PACKAGE_JAVA, false, "jclass class-metaobject") {
656            public LispObject execute(LispObject jclass, LispObject classMetaObject) {
657                return registerJavaClass((Class<?>) jclass.javaInstance(), classMetaObject);
658            }
659           
660        };
661
662    // ### +null+
663    public final static Symbol NULL
664        = Lisp.exportConstant("+NULL+", PACKAGE_JAVA, new JavaObject(null));
665    static {
666        String doc = "The JVM null object reference.";
667        NULL.setDocumentation(Symbol.VARIABLE, new SimpleString(doc));
668    }
669    // ### +true+
670    public final static Symbol TRUE
671        = Lisp.exportConstant("+TRUE+", PACKAGE_JAVA, new JavaObject(true));
672    static {
673        String doc = "The JVM primitive value for boolean true.";
674        TRUE.setDocumentation(Symbol.VARIABLE, new SimpleString(doc));
675    }
676    // ### +false+
677    public final static Symbol FALSE
678        = Lisp.exportConstant("+FALSE+", PACKAGE_JAVA, new JavaObject(false));
679    static {
680        String doc = "The JVM primitive value for boolean false.";
681        FALSE.setDocumentation(Symbol.VARIABLE, new SimpleString(doc));
682    }
683
684}
Note: See TracBrowser for help on using the repository browser.