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

Last change on this file since 12352 was 12352, checked in by astalla, 15 years ago

Have JavaObject?.javaInstance(c) complain if the wrapped object is null and c is a primitive type.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 15.1 KB
Line 
1/*
2 * JavaObject.java
3 *
4 * Copyright (C) 2002-2005 Peter Graves
5 * $Id: JavaObject.java 12352 2010-01-08 21:32:02Z 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.*;
39
40import java.math.BigInteger;
41
42import java.util.*;
43
44public final class JavaObject extends LispObject {
45    private 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    /**
55     * Constructs a Java Object with the given intended class, used to access
56     * the object reflectively. If the class represents a primitive type,
57     * the corresponding wrapper type is used instead.
58     * @throws ClassCastException if the object is not an instance of the
59     *                            intended class.
60     */
61    public JavaObject(Object obj, Class<?> intendedClass) {
62  if(obj != null && intendedClass == null) {
63      intendedClass = obj.getClass();
64  }
65  if(intendedClass != null) {
66      intendedClass = Java.maybeBoxClass(intendedClass);
67      if(!intendedClass.isInstance(obj)) {
68    throw new ClassCastException(obj + " can not be cast to " + intendedClass);
69      }
70  }
71  this.obj = obj;
72  this.intendedClass = intendedClass;
73    }
74
75    @Override
76    public LispObject typeOf()
77    {
78        return Symbol.JAVA_OBJECT;
79    }
80
81    @Override
82    public LispObject classOf()
83    {
84        if(obj == null) {
85                return BuiltInClass.JAVA_OBJECT;
86        } else {
87                return JavaClass.findJavaClass(obj.getClass());
88        }
89    }
90
91    @Override
92    public LispObject typep(LispObject type)
93    {
94        if (type == Symbol.JAVA_OBJECT)
95            return T;
96        if (type == BuiltInClass.JAVA_OBJECT)
97            return T;
98        if(type instanceof JavaClass && obj != null) {
99                return ((JavaClass) type).getJavaClass().isAssignableFrom(obj.getClass()) ? T : NIL;
100        }
101        return super.typep(type);
102    }
103
104    public final Object getObject()
105    {
106        return obj;
107    }
108
109    /** Encapsulates obj, if required.
110     * If obj is a {@link  LispObject}, it's returned as-is.
111     *
112     * @param obj Any java object
113     * @return obj or a new JavaObject encapsulating obj
114     */
115    public final static LispObject getInstance(Object obj) {
116        if (obj == null)
117            return new JavaObject(null);
118       
119        if (obj instanceof LispObject)
120            return (LispObject)obj;
121
122        return new JavaObject(obj);
123    }
124
125    /** Encapsulates obj, if required.
126     * If obj is a {@link LispObject}, it's returned as-is.
127     * If not, a java object with the specified intended class is returned.
128     *
129     * @param obj Any java object
130     * @param intendedClass the class that shall be used to access obj
131     * @return obj or a new JavaObject encapsulating obj
132     */
133    public final static LispObject getInstance(Object obj, Class<?> intendedClass) {
134        if (obj == null)
135            return new JavaObject(null);
136       
137        if (obj instanceof LispObject)
138            return (LispObject)obj;
139
140        return new JavaObject(obj, intendedClass);
141    }
142
143    /** Encapsulates obj, if required.
144     * If obj is a {@link LispObject}, it's returned as-is.
145     * If obj is of a type which can be mapped to a lisp type,
146     * an object of the mapped type is returned, if translated is true.
147     *
148     * @param obj
149     * @param translated
150     * @return a LispObject representing or encapsulating obj
151     */
152    public final static LispObject getInstance(Object obj, boolean translated) {
153  return getInstance(obj, translated, obj != null ? obj.getClass() : null);
154    }
155
156
157
158    /** Encapsulates obj, if required.
159     * If obj is a {@link LispObject}, it's returned as-is.
160     * If obj is of a type which can be mapped to a lisp type,
161     * an object of the mapped type is returned, if translated is true.
162     *
163     * @param obj
164     * @param translated
165     * @param intendedClass the class that shall be used to reflectively
166     *                      access obj; it is an error for obj not to be
167     *                      an instance of this class. This parameter is ignored
168     *                      if translated == true and the object can be
169     *                      converted to a Lisp object.
170     * @return a LispObject representing or encapsulating obj
171     */
172    public final static LispObject getInstance(Object obj, boolean translated, Class<?> intendedClass) {
173        if (! translated)
174            return getInstance(obj, intendedClass);
175
176        if (obj == null) return NIL;
177
178        if (obj instanceof LispObject)
179            return (LispObject)obj;
180
181        if (obj instanceof String)
182            return new SimpleString((String)obj);
183
184        if (obj instanceof Number) {
185            // Number types ordered according to decreasing
186            // estimated chances of occurrance
187
188            if (obj instanceof Integer)
189                return Fixnum.getInstance(((Integer)obj).intValue());
190
191            if (obj instanceof Float)
192                return new SingleFloat((Float)obj);
193
194            if (obj instanceof Double)
195                return new DoubleFloat((Double)obj);
196
197            if (obj instanceof Long)
198                return LispInteger.getInstance(((Long)obj).longValue());
199
200            if (obj instanceof BigInteger)
201                return Bignum.getInstance((BigInteger)obj);
202
203            if (obj instanceof Short)
204                return Fixnum.getInstance(((Short)obj).shortValue());
205
206            if (obj instanceof Byte)
207                return Fixnum.getInstance(((Byte)obj).byteValue());
208            // We don't handle BigDecimal: it doesn't map to a Lisp type
209        }
210
211        if (obj instanceof Boolean)
212            return ((Boolean)obj).booleanValue() ? T : NIL;
213
214        if (obj instanceof Character)
215            return new LispCharacter((Character)obj);
216
217        if (obj instanceof Object[]) {
218            Object[] array = (Object[]) obj;
219            SimpleVector v = new SimpleVector(array.length);
220            for (int i = array.length; i-- > 0;)
221                v.aset(i, JavaObject.getInstance(array[i], translated));
222            return v;
223        }
224        // TODO
225        // We might want to handle:
226        //  - streams
227        //  - others?
228        return new JavaObject(obj, intendedClass);
229    }
230
231    @Override
232    public Object javaInstance() {
233        return obj;
234    }
235
236    @Override
237    public Object javaInstance(Class c) {
238  if(obj == null) {
239      if(c.isPrimitive()) {
240    throw new NullPointerException("Cannot assign null to " + c);
241      }
242      return obj;
243  } else {
244      c = Java.maybeBoxClass(c);
245      if(c.isAssignableFrom(intendedClass)) {
246    return obj;
247      } else {
248    return error(new TypeError(intendedClass.getName() + " is not assignable to " + c.getName()));
249      }
250  }
251    }
252
253    /** Returns the encapsulated Java object for
254     * interoperability with wait, notify, synchronized, etc.
255     *
256     * @return The encapsulated object
257     */
258    @Override
259    public Object lockableInstance() {
260        return obj;
261    }
262
263    public Class<?> getIntendedClass() {
264  return intendedClass;
265    }
266
267    public static final Object getObject(LispObject o)
268
269    {
270        if (o instanceof JavaObject)
271                return ((JavaObject)o).obj;       
272        return             // Not reached.
273        type_error(o, Symbol.JAVA_OBJECT);       
274    }
275
276    @Override
277    public final boolean equal(LispObject other)
278    {
279        if (this == other)
280            return true;
281        if (other instanceof JavaObject)
282            return (obj == ((JavaObject)other).obj);
283        return false;
284    }
285
286    @Override
287    public final boolean equalp(LispObject other)
288    {
289        return equal(other);
290    }
291
292    @Override
293    public int sxhash()
294    {
295        return obj == null ? 0 : (obj.hashCode() & 0x7ffffff);
296    }
297
298    @Override
299    public String writeToString()
300    {
301        if (obj instanceof ControlTransfer)
302            return obj.toString();
303  final String s;
304  if(obj != null) {
305      Class<?> c = obj.getClass();
306      FastStringBuffer sb
307    = new FastStringBuffer(c.isArray() ? "jarray" : c.getName());
308      sb.append(' ');
309      String ts = obj.toString();
310      if(ts.length() > 32) { //random value, should be chosen sensibly
311    sb.append(ts.substring(0, 32) + "...");
312      } else {
313    sb.append(ts);
314      }
315      s = sb.toString();
316  } else {
317      s = "null";
318  }
319        return unreadableString(s);
320    }
321
322    @Override
323    public LispObject getDescription() {
324  return new SimpleString(describeJavaObject(this));
325    }
326
327    @Override
328    public LispObject getParts() {
329  if(obj != null) {
330      LispObject parts = NIL;
331      if(obj.getClass().isArray()) {
332    SimpleString empty = new SimpleString("");
333    int length = Array.getLength(obj);
334    for(int i = 0; i < length; i++) {
335        parts = parts.push
336      (new Cons(empty, JavaObject.getInstance(Array.get(obj, i))));
337    }
338    parts = parts.nreverse();
339      } else {
340    parts = parts.push(new Cons("Java class",
341              new JavaObject(obj.getClass())));
342    parts = Symbol.NCONC.execute(parts, getInspectedFields());
343      }
344      return parts;
345  } else {
346      return NIL;
347  }
348    }
349
350    private LispObject getInspectedFields()
351  {
352  final LispObject[] acc = new LispObject[] { NIL };
353  doClassHierarchy(obj.getClass(), new Function() {
354    @Override
355    public LispObject execute(LispObject arg)
356        {
357        //No possibility of type error - we're mapping this function
358        //over a list of classes
359        Class<?> c = (Class) arg.javaInstance();
360        for(Field f : c.getDeclaredFields()) {
361      LispObject value = NIL;
362      try {
363          if(!f.isAccessible()) {
364        f.setAccessible(true);
365          }
366          value = JavaObject.getInstance(f.get(obj));
367      } catch(Exception e) {}
368      acc[0] = acc[0].push(new Cons(f.getName(), value));
369        }
370        return acc[0];
371    }
372      });
373  return acc[0].nreverse();
374    }
375
376    /**
377     * Executes a function repeatedly over the minimal subtree of the
378     * Java class hierarchy which contains every class in <classes>.
379     */
380    private static void doClassHierarchy(Collection<Class<?>> classes,
381           LispObject callback,
382           Set<Class<?>> visited)
383  {
384  Collection<Class<?>> newClasses = new LinkedList<Class<?>>();
385  for(Class<?> clss : classes) {
386      if(clss == null) {
387    continue;
388      }
389      if(!visited.contains(clss)) {
390    callback.execute(JavaObject.getInstance(clss, true));
391    visited.add(clss);
392      }
393      if(!visited.contains(clss.getSuperclass())) {
394    newClasses.add(clss.getSuperclass());
395      }
396      for(Class<?> iface : clss.getInterfaces()) {
397    if (!visited.contains(iface)) {
398        newClasses.add(iface);
399    }
400      }
401  }
402  if(!newClasses.isEmpty()) {
403      doClassHierarchy(newClasses, callback, visited);
404  }
405    }
406
407    /**
408     * Executes a function recursively over <clss> and its superclasses and
409     * interfaces.
410     */
411    public static void doClassHierarchy(Class<?> clss, LispObject callback)
412  {
413  if (clss != null) {
414      Set<Class<?>> visited = new HashSet<Class<?>>();
415      Collection<Class<?>> classes = new ArrayList<Class<?>>(1);
416      classes.add(clss);
417      doClassHierarchy(classes, callback, visited);
418  }
419    }
420
421    public static LispObject mapcarClassHierarchy(Class<?> clss,
422              final LispObject fn)
423    {
424  final LispObject[] acc = new LispObject[] { NIL };
425  doClassHierarchy(clss, new Function() {
426    @Override
427    public LispObject execute(LispObject arg)
428        {
429        acc[0] = acc[0].push(fn.execute(arg));
430        return acc[0];
431    }
432      });
433  return acc[0].nreverse();
434    }
435
436    public static String describeJavaObject(final JavaObject javaObject)
437  {
438  final Object obj = javaObject.getObject();
439  final FastStringBuffer sb =
440      new FastStringBuffer(javaObject.writeToString());
441  sb.append(" is an object of type ");
442  sb.append(Symbol.JAVA_OBJECT.writeToString());
443  sb.append(".");
444  sb.append(System.getProperty("line.separator"));
445  sb.append("The wrapped Java object is ");
446  if (obj == null) {
447      sb.append("null.");
448  } else {
449      sb.append("an ");
450      final Class c = obj.getClass();
451      String className = c.getName();
452      if (c.isArray()) {
453    sb.append("array of ");
454    if (className.startsWith("[L") && className.endsWith(";")) {
455        className = className.substring(1, className.length() - 1);
456        sb.append(className);
457        sb.append(" objects");
458    } else if (className.startsWith("[") && className.length() > 1) {
459        char descriptor = className.charAt(1);
460        final String type;
461        switch (descriptor) {
462        case 'B': type = "bytes"; break;
463        case 'C': type = "chars"; break;
464        case 'D': type = "doubles"; break;
465        case 'F': type = "floats"; break;
466        case 'I': type = "ints"; break;
467        case 'J': type = "longs"; break;
468        case 'S': type = "shorts"; break;
469        case 'Z': type = "booleans"; break;
470        default:
471      type = "unknown type";
472        }
473        sb.append(type);
474    }
475    sb.append(" with ");
476    final int length = java.lang.reflect.Array.getLength(obj);
477    sb.append(length);
478    sb.append(" element");
479    if (length != 1)
480        sb.append('s');
481    sb.append('.');
482      } else {
483    sb.append("instance of ");
484    sb.append(className);
485    sb.append(':');
486    sb.append(System.getProperty("line.separator"));
487    sb.append("  \"");
488    sb.append(obj.toString());
489    sb.append('"');
490      }
491  }
492  return sb.toString();
493    }
494
495    // ### describe-java-object
496    private static final Primitive DESCRIBE_JAVA_OBJECT =
497        new Primitive("describe-java-object", PACKAGE_JAVA, true)
498    {
499        @Override
500        public LispObject execute(LispObject first, LispObject second)
501
502        {
503            if (!(first instanceof JavaObject))
504                return type_error(first, Symbol.JAVA_OBJECT);
505            final Stream stream = checkStream(second);
506            final JavaObject javaObject = (JavaObject) first;
507            stream._writeString(describeJavaObject(javaObject));
508            return LispThread.currentThread().nothing();
509        }
510    };
511}
Note: See TracBrowser for help on using the repository browser.