source: branches/streams/abcl/src/org/armedbear/lisp/JavaObject.java

Last change on this file was 14600, checked in by Mark Evenson, 11 years ago

Signal Java-side exceptions caught in JavaObject?.printObject() as a Lisp error.

From ferada on #abcl: toString() should be caught, otherwise abcl
just stops http://paste.lisp.org/display/140719.

  • 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 14600 2014-01-09 11:22:38Z 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      try {
368        String ts = obj.toString();
369        int length = -1;
370        LispObject stringLength = _JAVA_OBJECT_TO_STRING_LENGTH.symbolValueNoThrow();
371        if (stringLength instanceof Fixnum) {
372          length = Fixnum.getValue(stringLength);
373        }
374        if (length < 0) {
375          sb.append(ts);
376        } else if (ts.length() > length) { 
377          // use '....' to not confuse user with PPRINT conventions
378          sb.append(ts.substring(0, length)).append("...."); 
379        } else {
380          sb.append(ts);
381        }
382        s = sb.toString();
383      } catch (Exception e) {
384        return serror(new JavaException(e));
385      }
386        } else {
387            s = "null";
388        }
389        return unreadableString(s);
390    }
391
392    @Override
393    public LispObject getDescription() {
394        return new SimpleString(describeJavaObject(this));
395    }
396
397    @Override
398    public LispObject getParts() {
399        if(obj != null) {
400            LispObject parts = NIL;
401            parts = parts.push(new Cons("Java class",
402                                        new JavaObject(obj.getClass())));
403            if (intendedClass != null) {
404                parts = parts.push(new Cons("intendedClass", new SimpleString(intendedClass.getCanonicalName())));
405            }
406            if (obj.getClass().isArray()) {
407                int length = Array.getLength(obj);
408                for (int i = 0; i < length; i++) {
409                    parts = parts
410                        .push(new Cons(new SimpleString(i), 
411                                       JavaObject.getInstance(Array.get(obj, i))));
412                }
413            } else {
414                parts = Symbol.NCONC.execute(parts, getInspectedFields());
415            }
416            if (obj instanceof java.lang.Class) {
417                Class o = (java.lang.Class)obj;
418                try {
419                    Class[] classes = o.getClasses();
420                    LispObject classesList = NIL;
421                    for (int i = 0; i < classes.length; i++) {
422                        classesList = classesList.push(JavaObject.getInstance(classes[i]));
423                    }
424                    if (!classesList.equals(NIL)) {
425                        parts = parts
426                            .push(new Cons("Member classes", classesList.nreverse()));
427                    }
428                } catch (SecurityException e) {
429                    Debug.trace(e);
430                }
431                Class[] interfaces = o.getInterfaces();
432                LispObject interfacesList = NIL;
433                for (int i = 0; i < interfaces.length; i++) {
434                    interfacesList = interfacesList.push(JavaObject.getInstance(interfaces[i]));
435                }
436                if (!interfacesList.equals(NIL)) {
437                    parts = parts
438                        .push(new Cons("Interfaces", interfacesList.nreverse()));
439                }
440                LispObject superclassList = NIL;
441                Class superclass = o.getSuperclass();
442                while (superclass != null) {
443                    superclassList = superclassList.push(JavaObject.getInstance(superclass));
444                    superclass = superclass.getSuperclass();
445                }
446                if (!superclassList.equals(NIL)) {
447                    parts = parts
448                        .push(new Cons("Superclasses", superclassList.nreverse()));
449                }
450            }
451            return parts.nreverse();
452        } else {
453            return NIL;
454        }
455    }
456
457    private LispObject getInspectedFields()
458        {
459        final LispObject[] acc = new LispObject[] { NIL };
460        doClassHierarchy(obj.getClass(), new Function() {
461                @Override
462                public LispObject execute(LispObject arg)
463                    {
464                    //No possibility of type error - we're mapping this function
465                    //over a list of classes
466                    Class<?> c = (Class) arg.javaInstance();
467                    for(Field f : c.getDeclaredFields()) {
468                        LispObject value = NIL;
469                        try {
470                            if(!f.isAccessible()) {
471                                f.setAccessible(true);
472                            }
473                            value = JavaObject.getInstance(f.get(obj));
474                        } catch(Exception e) {}
475                        acc[0] = acc[0].push(new Cons(f.getName(), value));
476                    }
477                    return acc[0];
478                }
479            });
480        return acc[0].nreverse();
481    }
482
483    /**
484     * Executes a function repeatedly over the minimal subtree of the
485     * Java class hierarchy which contains every class in <classes>.
486     */
487    private static void doClassHierarchy(Collection<Class<?>> classes,
488                                         LispObject callback,
489                                         Set<Class<?>> visited)
490        {
491        Collection<Class<?>> newClasses = new LinkedList<Class<?>>();
492        for(Class<?> clss : classes) {
493            if(clss == null) {
494                continue;
495            }
496            if(!visited.contains(clss)) {
497                callback.execute(JavaObject.getInstance(clss, true));
498                visited.add(clss);
499            }
500            if(!visited.contains(clss.getSuperclass())) {
501                newClasses.add(clss.getSuperclass());
502            }
503            for(Class<?> iface : clss.getInterfaces()) {
504                if (!visited.contains(iface)) {
505                    newClasses.add(iface);
506                }
507            }
508        }
509        if(!newClasses.isEmpty()) {
510            doClassHierarchy(newClasses, callback, visited);
511        }
512    }
513
514    /**
515     * Executes a function recursively over <clss> and its superclasses and
516     * interfaces.
517     */
518    public static void doClassHierarchy(Class<?> clss, LispObject callback)
519        {
520        if (clss != null) {
521            Set<Class<?>> visited = new HashSet<Class<?>>();
522            Collection<Class<?>> classes = new ArrayList<Class<?>>(1);
523            classes.add(clss);
524            doClassHierarchy(classes, callback, visited);
525        }
526    }
527
528    public static LispObject mapcarClassHierarchy(Class<?> clss,
529                                                  final LispObject fn)
530    {
531        final LispObject[] acc = new LispObject[] { NIL };
532        doClassHierarchy(clss, new Function() {
533                @Override
534                public LispObject execute(LispObject arg)
535                    {
536                    acc[0] = acc[0].push(fn.execute(arg));
537                    return acc[0];
538                }
539            });
540        return acc[0].nreverse();
541    }
542
543    public static String describeJavaObject(final JavaObject javaObject)
544        {
545        final Object obj = javaObject.getObject();
546        final StringBuilder sb =
547            new StringBuilder(javaObject.princToString());
548        sb.append(" is an object of type ");
549        sb.append(Symbol.JAVA_OBJECT.princToString());
550        sb.append(".");
551        sb.append(System.getProperty("line.separator"));
552        sb.append("The wrapped Java object is ");
553        if (obj == null) {
554            sb.append("null.");
555        } else {
556            sb.append("an ");
557            final Class c = obj.getClass();
558            String className = c.getName();
559            if (c.isArray()) {
560                sb.append("array of ");
561                if (className.startsWith("[L") && className.endsWith(";")) {
562                    className = className.substring(1, className.length() - 1);
563                    sb.append(className);
564                    sb.append(" objects");
565                } else if (className.startsWith("[") && className.length() > 1) {
566                    char descriptor = className.charAt(1);
567                    final String type;
568                    switch (descriptor) {
569                    case 'B': type = "bytes"; break;
570                    case 'C': type = "chars"; break;
571                    case 'D': type = "doubles"; break;
572                    case 'F': type = "floats"; break;
573                    case 'I': type = "ints"; break;
574                    case 'J': type = "longs"; break;
575                    case 'S': type = "shorts"; break;
576                    case 'Z': type = "booleans"; break;
577                    default:
578                        type = "unknown type";
579                    }
580                    sb.append(type);
581                }
582                sb.append(" with ");
583                final int length = java.lang.reflect.Array.getLength(obj);
584                sb.append(length);
585                sb.append(" element");
586                if (length != 1)
587                    sb.append('s');
588                sb.append('.');
589            } else {
590                sb.append("instance of ");
591                sb.append(className);
592                sb.append(':');
593                sb.append(System.getProperty("line.separator"));
594                sb.append("  \"");
595                sb.append(obj.toString());
596                sb.append('"');
597            }
598        }
599        return sb.toString();
600    }
601
602    // ### describe-java-object
603    private static final Primitive DESCRIBE_JAVA_OBJECT =
604        new Primitive("describe-java-object", PACKAGE_JAVA, true)
605    {
606        @Override
607        public LispObject execute(LispObject first, LispObject second)
608
609        {
610            if (!(first instanceof JavaObject))
611                return type_error(first, Symbol.JAVA_OBJECT);
612            final Stream stream = checkStream(second);
613            final JavaObject javaObject = (JavaObject) first;
614            stream._writeString(describeJavaObject(javaObject));
615            return LispThread.currentThread().nothing();
616        }
617    };
618
619    //JAVA-CLASS support
620
621    //There is no point for this Map to be weak since values keep a reference to the corresponding
622    //key (the Java class). This should not be a problem since Java classes are limited in number -
623    //if they grew indefinitely, the JVM itself would crash.
624    private static final Map<Class<?>, LispObject> javaClassMap = new HashMap<Class<?>, LispObject>();
625
626    public static LispObject registerJavaClass(Class<?> javaClass, LispObject classMetaObject) {
627        synchronized (javaClassMap) {
628            javaClassMap.put(javaClass, classMetaObject);
629            return classMetaObject;
630        }
631    }
632
633    public static LispObject findJavaClass(Class<?> javaClass) {
634        synchronized (javaClassMap) {
635            LispObject c = javaClassMap.get(javaClass);
636            if (c != null) {
637                return c;
638            } else {
639                return NIL;
640            }
641        }
642    }
643
644    private static final Primitive _FIND_JAVA_CLASS = new Primitive("%find-java-class", PACKAGE_JAVA, false, "class-name-or-class") {
645            public LispObject execute(LispObject arg) {
646                try {
647                    if(arg instanceof AbstractString) {
648                        return findJavaClass(Class.forName((String) arg.getStringValue()));
649                    } else {
650                        return findJavaClass((Class<?>) arg.javaInstance());
651                    }
652                } catch (ClassNotFoundException e) {
653                    return error(new LispError("Cannot find Java class " + arg.getStringValue()));
654                }
655            }
656           
657        };
658
659    private static final Primitive _REGISTER_JAVA_CLASS = new Primitive("%register-java-class", PACKAGE_JAVA, false, "jclass class-metaobject") {
660            public LispObject execute(LispObject jclass, LispObject classMetaObject) {
661                return registerJavaClass((Class<?>) jclass.javaInstance(), classMetaObject);
662            }
663           
664        };
665
666    // ### +null+
667    public final static Symbol NULL
668        = Lisp.exportConstant("+NULL+", PACKAGE_JAVA, new JavaObject(null));
669    static {
670        String doc = "The JVM null object reference.";
671        NULL.setDocumentation(Symbol.VARIABLE, new SimpleString(doc));
672    }
673    // ### +true+
674    public final static Symbol TRUE
675        = Lisp.exportConstant("+TRUE+", PACKAGE_JAVA, new JavaObject(true));
676    static {
677        String doc = "The JVM primitive value for boolean true.";
678        TRUE.setDocumentation(Symbol.VARIABLE, new SimpleString(doc));
679    }
680    // ### +false+
681    public final static Symbol FALSE
682        = Lisp.exportConstant("+FALSE+", PACKAGE_JAVA, new JavaObject(false));
683    static {
684        String doc = "The JVM primitive value for boolean false.";
685        FALSE.setDocumentation(Symbol.VARIABLE, new SimpleString(doc));
686    }
687
688}
Note: See TracBrowser for help on using the repository browser.