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

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

JAVA-CLASS metaclass reimplemented in Lisp.

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