source: trunk/abcl/src/org/armedbear/lisp/JavaObject.java

Last change on this file was 15569, checked in by Mark Evenson, 2 years ago

Untabify en masse

Results of running style.org source blocks on tree

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