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

Last change on this file was 12831, checked in by astalla, 14 years ago

First stab at Java collections integration with the sequences protocol.

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