source: branches/1.3.1/src/org/armedbear/lisp/Java.java @ 14683

Last change on this file since 14683 was 14683, checked in by mevenson, 3 years ago

Backport r14682: Make JCALL work in more places.

A reimplementation of org.apache.commons.lang.ClassUtils?.isAssignable
instead of the standard isAssignableFrom test.

<http://abcl.org/trac/ticket/352> .

From Olof.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 50.8 KB
Line 
1/*
2 * Java.java
3 *
4 * Copyright (C) 2002-2006 Peter Graves, Andras Simon
5 * $Id: Java.java 14683 2014-04-17 11:50:49Z 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.Constructor;
40import java.lang.reflect.Field;
41import java.lang.reflect.InvocationTargetException;
42import java.lang.reflect.Method;
43import java.lang.reflect.Modifier;
44import java.text.MessageFormat;
45import java.util.*;
46
47public final class Java
48{
49    static final Map<Class,Symbol> registeredExceptions =
50       new HashMap<Class,Symbol>();
51
52    private static final LispClass java_exception = LispClass.findClass(Symbol.JAVA_EXCEPTION);
53
54    static boolean isJavaException(LispClass lc)
55    {
56        return lc.subclassp(java_exception);
57    }
58
59    private static final Primitive ENSURE_JAVA_OBJECT = new pf_ensure_java_object();
60    @DocString(name="ensure-java-object", args="obj",
61    doc="Ensures OBJ is wrapped in a JAVA-OBJECT, wrapping it if necessary.")
62    private static final class pf_ensure_java_object extends Primitive
63    {
64        pf_ensure_java_object()
65        {
66            super("ensure-java-object", PACKAGE_JAVA, true);
67        }
68
69        @Override
70        public LispObject execute(LispObject obj) {
71            return obj instanceof JavaObject ? obj : new JavaObject(obj);
72        }
73    };
74
75    private static final Primitive REGISTER_JAVA_EXCEPTION = new pf_register_java_exception();
76    @DocString(name="register-java-exception", // => T
77    args="exception-name condition-symbol",
78    doc="Registers the Java Throwable named by the symbol EXCEPTION-NAME as the condition " +
79        "designated by CONDITION-SYMBOL.  Returns T if successful, NIL if not.")
80    private static final class pf_register_java_exception extends Primitive
81    {
82        pf_register_java_exception()
83        {
84            super("register-java-exception", PACKAGE_JAVA, true);
85        }
86
87        @Override
88        public LispObject execute(LispObject className, LispObject symbol)
89
90        {
91            LispClass lispClass = (LispClass) LispClass.findClass(symbol, true);
92            // FIXME Signal a continuable error if the exception is already registered.
93            if (isJavaException(lispClass)) {
94                registeredExceptions.put(classForName(className.getStringValue()),
95                                         (Symbol)symbol);
96                return T;
97            }
98            return NIL;
99        }
100    };
101
102    private static final Primitive UNREGISTER_JAVA_EXCEPTION = new pf_unregister_java_exception();
103    @DocString(name="unregister-java-exception", args="exception-name",
104    doc="Unregisters the Java Throwable EXCEPTION-NAME previously registered" +
105        " by REGISTER-JAVA-EXCEPTION.")
106    private static final class pf_unregister_java_exception extends Primitive
107    {
108        pf_unregister_java_exception()
109        {
110            super("unregister-java-exception", PACKAGE_JAVA, true);
111        }
112
113        @Override
114        public LispObject execute(LispObject className)
115
116        {
117            // FIXME Verify that EXCEPTION-NAME designates a subclass of Throwable.
118            return registeredExceptions.remove(classForName(className.getStringValue())) == null ? NIL : T;
119        }
120    };
121
122    static Symbol getCondition(Class cl) {
123        Class o = classForName("java.lang.Object");
124        for (Class c = cl ; c != o ; c = c.getSuperclass()) {
125            Object object = registeredExceptions.get(c);
126            if (object instanceof Symbol) {
127                LispClass lispClass = (LispClass) LispClass.findClass((Symbol) object, true);
128                if(isJavaException(lispClass)) {
129                    return (Symbol) object;
130                }
131            }
132        }
133        return null;
134    }
135
136    private static final Primitive JCLASS = new pf_jclass();
137    @DocString(name="jclass", args="name-or-class-ref &optional class-loader",
138    doc="Returns a reference to the Java class designated by" +
139        " NAME-OR-CLASS-REF. If the CLASS-LOADER parameter is passed, the" +
140        " class is resolved with respect to the given ClassLoader.")
141    private static final class pf_jclass extends Primitive
142    {
143
144        pf_jclass() 
145        {
146            super(Symbol.JCLASS);
147        }
148
149        @Override
150        public LispObject execute(LispObject arg)
151        {
152      return JavaObject.getInstance(javaClass(arg, JavaClassLoader.getCurrentClassLoader()));
153        }
154
155        @Override
156        public LispObject execute(LispObject className, LispObject classLoader)
157        {
158      ClassLoader loader = (ClassLoader) classLoader.javaInstance(ClassLoader.class);
159      return JavaObject.getInstance(javaClass(className, loader));
160        }
161    };
162
163    static final LispObject jfield(Primitive fun, LispObject[] args, boolean translate)
164
165    {
166        if (args.length < 2 || args.length > 4)
167            error(new WrongNumberOfArgumentsException(fun, 2, 4));
168        String fieldName = null;
169        Class c;
170        Field f;
171        Class fieldType;
172        Object instance = null;
173        try {
174            if (args[1] instanceof AbstractString) {
175                // Cases 1-5.
176                fieldName = args[1].getStringValue();
177                c = javaClass(args[0]);
178            } else {
179                // Cases 6 and 7.
180                fieldName = args[0].getStringValue();
181                instance = JavaObject.getObject(args[1]);
182                c = instance.getClass();
183            }
184            f = c.getField(fieldName);
185            fieldType = f.getType();
186            switch (args.length) {
187                case 2:
188                    // Cases 1 and 6.
189                    break;
190                case 3:
191                    // Cases 2,3, and 7.
192                    if (instance == null) {
193                        // Cases 2 and 3.
194                        if (args[2] instanceof JavaObject) {
195                            // Case 2.
196                            instance = JavaObject.getObject(args[2]);
197                            break;
198                        } else {
199                            // Case 3.
200                            f.set(null,args[2].javaInstance(fieldType));
201                            return args[2];
202                        }
203                    } else {
204                        // Case 7.
205                        f.set(instance,args[2].javaInstance(fieldType));
206                        return args[2];
207                    }
208                case 4:
209                    // Cases 4 and 5.
210                    if (args[2] != NIL) {
211                        // Case 4.
212                        instance = JavaObject.getObject(args[2]);
213                    }
214                    f.set(instance,args[3].javaInstance(fieldType));
215                    return args[3];
216            }
217            return JavaObject.getInstance(f.get(instance), translate, f.getType());
218        }
219        catch (NoSuchFieldException e) {
220            error(new LispError("no such field"));
221        }
222        catch (SecurityException e) {
223            error(new LispError("inaccessible field"));
224        }
225        catch (IllegalAccessException e) {
226            error(new LispError("illegal access"));
227        }
228        catch (IllegalArgumentException e) {
229            error(new LispError("illegal argument"));
230        }
231        catch (Throwable t) { // no code -> no ControlTransfer
232            error(new LispError(getMessage(t)));
233        }
234        // Not reached.
235        return NIL;
236    }
237
238
239    private static final Primitive JFIELD = new pf_jfield();
240    @DocString(name="jfield",
241    args="class-ref-or-field field-or-instance &optional instance value",
242    doc="Retrieves or modifies a field in a Java class or instance.\n\n"+
243        "Supported argument patterns:\n\n"+
244        "   Case 1: class-ref  field-name:\n"+
245        "      Retrieves the value of a static field.\n\n"+
246        "   Case 2: class-ref  field-name  instance-ref:\n"+
247        "      Retrieves the value of a class field of the instance.\n\n"+
248        "   Case 3: class-ref  field-name  primitive-value:\n"+
249        "      Stores a primitive-value in a static field.\n\n"+
250        "   Case 4: class-ref  field-name  instance-ref  value:\n"+
251        "      Stores value in a class field of the instance.\n\n"+
252        "   Case 5: class-ref  field-name  nil  value:\n"+
253        "      Stores value in a static field (when value may be\n"+
254        "      confused with an instance-ref).\n\n"+
255        "   Case 6: field-name  instance:\n"+
256        "      Retrieves the value of a field of the instance. The\n"+
257        "      class is derived from the instance.\n\n"+
258        "   Case 7: field-name  instance  value:\n"+
259        "      Stores value in a field of the instance. The class is\n"+
260        "      derived from the instance.\n\n"
261        )
262    private static final class pf_jfield extends Primitive
263    {
264        pf_jfield() 
265        {
266            super("jfield", PACKAGE_JAVA, true);
267        }
268
269        @Override
270        public LispObject execute(LispObject[] args)
271        {
272            return jfield(this, args, true);
273        }
274    };
275
276    private static final Primitive JFIELD_RAW = new pf_jfield_raw();
277    @DocString(name="jfield",
278    args="class-ref-or-field field-or-instance &optional instance value",
279    doc="Retrieves or modifies a field in a Java class or instance. Does not\n"+
280        "attempt to coerce its value or the result into a Lisp object.\n\n"+
281        "Supported argument patterns:\n\n"+
282        "   Case 1: class-ref  field-name:\n"+
283        "      Retrieves the value of a static field.\n\n"+
284        "   Case 2: class-ref  field-name  instance-ref:\n"+
285        "      Retrieves the value of a class field of the instance.\n\n"+
286        "   Case 3: class-ref  field-name  primitive-value:\n"+
287        "      Stores a primitive-value in a static field.\n\n"+
288        "   Case 4: class-ref  field-name  instance-ref  value:\n"+
289        "      Stores value in a class field of the instance.\n\n"+
290        "   Case 5: class-ref  field-name  nil  value:\n"+
291        "      Stores value in a static field (when value may be\n"+
292        "      confused with an instance-ref).\n\n"+
293        "   Case 6: field-name  instance:\n"+
294        "      Retrieves the value of a field of the instance. The\n"+
295        "      class is derived from the instance.\n\n"+
296        "   Case 7: field-name  instance  value:\n"+
297        "      Stores value in a field of the instance. The class is\n"+
298        "      derived from the instance.\n\n"
299        )
300    private static final class pf_jfield_raw extends Primitive
301    {
302        pf_jfield_raw() 
303        {
304            super("jfield-raw", PACKAGE_JAVA, true);
305        }
306
307        @Override
308        public LispObject execute(LispObject[] args)
309        {
310            return jfield(this, args, false);
311        }
312    };
313
314    private static final Primitive JCONSTRUCTOR = new pf_jconstructor();
315    @DocString(name="jconstructor", args="class-ref &rest parameter-class-refs",
316    doc="Returns a reference to the Java constructor of CLASS-REF with the" +
317        " given PARAMETER-CLASS-REFS.")
318    private static final class pf_jconstructor extends Primitive
319    {
320        pf_jconstructor() 
321        {
322            super("jconstructor", PACKAGE_JAVA, true);
323        }
324
325        @Override
326        public LispObject execute(LispObject[] args)
327        {
328            if (args.length < 1)
329                error(new WrongNumberOfArgumentsException(this, 1, -1));
330            try {
331                final Class<?> c = javaClass(args[0]);
332                int argCount = 0;
333                if (args.length == 2 && args[1] instanceof Fixnum) {
334                    argCount = Fixnum.getValue(args[1]);
335                } else {
336                    Class<?>[] parameterTypes = new Class[args.length-1];
337                    for (int i = 1; i < args.length; i++) {
338                        parameterTypes[i-1] = javaClass(args[i]);
339                    }
340                    return JavaObject.getInstance(c.getConstructor(parameterTypes));
341                }
342                // Parameter types not explicitly specified.
343                Constructor[] constructors = c.getConstructors();
344                for (int i = 0; i < constructors.length; i++) {
345                    Constructor constructor = constructors[i];
346                    if (constructor.getParameterTypes().length == argCount)
347                        return JavaObject.getInstance(constructor);
348                }
349                throw new NoSuchMethodException();
350            }
351            catch (NoSuchMethodException e) {
352                error(new LispError("no such constructor"));
353            }
354            catch (ControlTransfer e) {
355                throw e;
356            }
357            catch (Throwable t) { // ControlTransfer addressed above
358                error(new LispError(getMessage(t)));
359            }
360            // Not reached.
361            return NIL;
362        }
363    };
364
365    private static final Primitive JMETHOD = new pf_jmethod();
366
367    @DocString(name="jmethod", args="class-ref method-name &rest parameter-class-refs",
368    doc="Returns a reference to the Java method METHOD-NAME of CLASS-REF with the" +
369        " given PARAMETER-CLASS-REFS.")
370    private static final class pf_jmethod extends Primitive
371    {
372        pf_jmethod() 
373        {
374            super("jmethod", PACKAGE_JAVA, true);
375        }
376
377        @Override
378        public LispObject execute(LispObject[] args)
379        {
380            if (args.length < 2)
381                error(new WrongNumberOfArgumentsException(this, 2, -1));
382            final Class<?> c = javaClass(args[0]);
383            String methodName = args[1].getStringValue();
384            try {
385                int argCount = 0;
386                if (args.length == 3 && args[2] instanceof Fixnum) {
387                    argCount = ((Fixnum)args[2]).value;
388                } else {
389                    Class<?>[] parameterTypes = new Class[args.length-2];
390                    for (int i = 2; i < args.length; i++)
391                        parameterTypes[i-2] = javaClass(args[i]);
392                    return JavaObject.getInstance(c.getMethod(methodName,
393                                                              parameterTypes));
394                }
395                // Parameter types were not explicitly specified.
396                Method[] methods = c.getMethods();
397                for (int i = 0; i < methods.length; i++) {
398                    Method method = methods[i];
399                    if (method.getName().equals(methodName) &&
400                        method.getParameterTypes().length == argCount)
401                        return JavaObject.getInstance(method);
402                }
403                throw new NoSuchMethodException();
404            }
405            catch (NoSuchMethodException e) {
406                StringBuilder sb = new StringBuilder("No such method: ");
407                sb.append(c.getName());
408                sb.append('.');
409                sb.append(methodName);
410                sb.append('(');
411                for (int i = 2; i < args.length; i++) {
412                    sb.append(args[i].princToString());
413                    if (i < args.length - 1)
414                        sb.append(',');
415                }
416                sb.append(')');
417                error(new LispError(sb.toString()));
418            }
419            catch (ControlTransfer e) {
420                throw e;
421            }
422            catch (Throwable t) { // ControlTransfer addressed above
423                error(new LispError(getMessage(t)));
424            }
425            // Not reached.
426            return NIL;
427        }
428    };
429
430    static final LispObject jstatic(Primitive fun, LispObject[] args, boolean translate)
431
432    {
433        if (args.length < 2)
434            error(new WrongNumberOfArgumentsException(fun, 2, -1));
435        try {
436            Method m = null;
437            LispObject methodRef = args[0];
438            if (methodRef instanceof JavaObject) {
439                Object obj = ((JavaObject)methodRef).getObject();
440                if (obj instanceof Method)
441                    m = (Method) obj;
442            } else if (methodRef instanceof AbstractString) {
443                Class c = javaClass(args[1]);
444                if (c != null) {
445                    String methodName = methodRef.getStringValue();
446                    Method[] methods = c.getMethods();
447                    List<Method> staticMethods = new ArrayList<Method>();
448                    int argCount = args.length - 2;
449                    for(Method m1 : methods) {
450                        if(Modifier.isStatic(m1.getModifiers())) {
451                            staticMethods.add(m1);
452                        }
453                    }
454                    if(staticMethods.size() > 0) {
455                        m = findMethod(staticMethods.toArray(new Method[staticMethods.size()]), methodName, args, 2);
456                    }
457                    if (m == null)
458                        error(new LispError("no such method"));
459                }
460            } else
461              type_error(methodRef, Symbol.STRING);
462            Object[] methodArgs = new Object[args.length-2];
463            Class[] argTypes = m.getParameterTypes();
464            for (int i = 2; i < args.length; i++) {
465                LispObject arg = args[i];
466                if (arg == NIL)
467                    methodArgs[i-2] = null;
468                else
469                    methodArgs[i-2] = arg.javaInstance(argTypes[i-2]);
470            }
471            m.setAccessible(true);
472            Object result = m.invoke(null, methodArgs);
473      return JavaObject.getInstance(result, translate, m.getReturnType());
474        }
475        catch (ControlTransfer c) {
476            throw c;
477        }
478        catch (Throwable t) { // ControlTransfer handled above
479            if (t instanceof InvocationTargetException)
480                t = t.getCause();
481            Symbol condition = getCondition(t.getClass());
482            if (condition == null)
483                error(new JavaException(t));
484            else
485                Symbol.SIGNAL.execute(
486                    condition,
487                    Keyword.CAUSE,
488                    JavaObject.getInstance(t),
489                    Keyword.FORMAT_CONTROL,
490                    new SimpleString(getMessage(t)));
491        }
492        // Not reached.
493        return NIL;
494    }
495
496    private static final Primitive JSTATIC = new pf_jstatic();
497    @DocString(name="jstatic", args="method class &rest args",
498    doc="Invokes the static method METHOD on class CLASS with ARGS.")
499    private static final class pf_jstatic extends Primitive
500    {
501        pf_jstatic() 
502        {
503            super("jstatic", PACKAGE_JAVA, true);
504        }
505
506        @Override
507        public LispObject execute(LispObject[] args)
508        {
509            return jstatic(this, args, true);
510        }
511    };
512
513    private static final Primitive JSTATIC_RAW = new pf_jstatic_raw();
514    @DocString(name="jstatic-raw", args="method class &rest args",
515    doc="Invokes the static method METHOD on class CLASS with ARGS. Does not "+
516        "attempt to coerce the arguments or result into a Lisp object.")
517    private static final class pf_jstatic_raw extends Primitive
518    {
519        pf_jstatic_raw() 
520        {
521            super("jstatic-raw", PACKAGE_JAVA, true);
522        }
523
524        @Override
525        public LispObject execute(LispObject[] args)
526        {
527            return jstatic(this, args, false);
528        }
529    };
530
531    private static final Primitive JNEW = new pf_jnew();
532    @DocString(name="jnew", args="constructor &rest args",
533    doc="Invokes the Java constructor CONSTRUCTOR with the arguments ARGS.")
534    private static final class pf_jnew extends Primitive
535    {
536        pf_jnew()
537        {
538            super("jnew", PACKAGE_JAVA, true);
539        }
540
541        @Override
542        public LispObject execute(LispObject[] args)
543        {
544            if (args.length < 1)
545                error(new WrongNumberOfArgumentsException(this, 1, -1));
546            LispObject classRef = args[0];
547            try {
548                Constructor constructor;
549    if(classRef instanceof AbstractString) {
550        constructor = findConstructor(javaClass(classRef), args);
551    } else {
552        Object object = JavaObject.getObject(classRef);
553        if(object instanceof Constructor) {
554      constructor = (Constructor) object;
555        } else if(object instanceof Class<?>) {
556      constructor = findConstructor((Class<?>) object, args);
557        } else {
558      return error(new LispError(classRef.princToString() + " is neither a Constructor nor a Class"));
559        }
560    }
561                Class[] argTypes = constructor.getParameterTypes();
562                Object[] initargs = new Object[args.length-1];
563                for (int i = 1; i < args.length; i++) {
564                    LispObject arg = args[i];
565                    if (arg == NIL)
566                        initargs[i-1] = null;
567                    else {
568                        initargs[i-1] = arg.javaInstance(argTypes[i-1]);
569                    }
570                }
571                return JavaObject.getInstance(constructor.newInstance(initargs));
572            }
573            catch (ControlTransfer c) {
574                throw c;
575            }
576            catch (Throwable t) { // ControlTransfer handled above
577                if (t instanceof InvocationTargetException)
578                    t = t.getCause();
579                Symbol condition = getCondition(t.getClass());
580                if (condition == null)
581                    error(new JavaException(t));
582                else
583                    Symbol.SIGNAL.execute(
584                        condition,
585                        Keyword.CAUSE,
586                        JavaObject.getInstance(t),
587                        Keyword.FORMAT_CONTROL,
588                        new SimpleString(getMessage(t)));
589            }
590            // Not reached.
591            return NIL;
592        }
593    };
594
595    private static final Primitive JNEW_ARRAY = new pf_jnew_array();
596    @DocString(name="jnew-array", args="element-type &rest dimensions",
597    doc="Creates a new Java array of type ELEMENT-TYPE, with the given" +
598        " DIMENSIONS.")
599    private static final class pf_jnew_array extends Primitive
600    {
601        pf_jnew_array()
602        {
603            super("jnew-array", PACKAGE_JAVA, true);
604        }
605
606        @Override
607        public LispObject execute(LispObject[] args)
608        {
609            if (args.length < 2)
610                error(new WrongNumberOfArgumentsException(this, 2, -1));
611            try {
612                Class c = javaClass(args[0]);
613                int[] dimensions = new int[args.length - 1];
614                for (int i = 1; i < args.length; i++)
615                    dimensions[i-1] = ((Integer)args[i].javaInstance()).intValue();
616                return JavaObject.getInstance(Array.newInstance(c, dimensions));
617            }
618            catch (Throwable t) { // no code -> no ControlTransfer
619                error(new JavaException(t));
620            }
621            // Not reached.
622            return NIL;
623        }
624    };
625
626    static final LispObject jarray_ref(Primitive fun, LispObject[] args, boolean translate)
627
628    {
629        if (args.length < 2)
630            error(new WrongNumberOfArgumentsException(fun, 2, -1));
631        try {
632            Object a = args[0].javaInstance();
633            for (int i = 1; i<args.length - 1; i++)
634                a = Array.get(a, ((Integer)args[i].javaInstance()).intValue());
635            return JavaObject.getInstance(Array.get(a,
636                    ((Integer)args[args.length - 1].javaInstance()).intValue()), translate);
637        }
638        catch (Throwable t) { // no code -> no ControlTransfer
639            Symbol condition = getCondition(t.getClass());
640            if (condition == null)
641                error(new JavaException(t));
642            else
643                Symbol.SIGNAL.execute(
644                    condition,
645                    Keyword.CAUSE,
646                    JavaObject.getInstance(t),
647                    Keyword.FORMAT_CONTROL,
648                    new SimpleString(getMessage(t)));
649        }
650        // Not reached.
651        return NIL;
652    }
653
654    private static final Primitive JARRAY_REF = new pf_jarray_ref();
655    @DocString(name="jarray-ref", args="java-array &rest indices",
656    doc="Dereferences the Java array JAVA-ARRAY using the given INDICIES, " +
657        "coercing the result into a Lisp object, if possible.")
658    private static final class pf_jarray_ref extends Primitive
659    {
660        pf_jarray_ref()
661        {
662            super("jarray-ref", PACKAGE_JAVA, true);
663        }
664
665        @Override
666        public LispObject execute(LispObject[] args)
667        {
668            return jarray_ref(this, args, true);
669        }
670    };
671
672    private static final Primitive JARRAY_REF_RAW = new pf_jarray_ref_raw();
673    @DocString(name="jarray-ref-raw", args="java-array &rest indices",
674    doc="Dereference the Java array JAVA-ARRAY using the given INDICIES. " +
675        "Does not attempt to coerce the result into a Lisp object.")
676    private static final class pf_jarray_ref_raw extends Primitive
677    {
678        pf_jarray_ref_raw() 
679        {
680            super("jarray-ref-raw", PACKAGE_JAVA, true);
681        }
682
683        @Override
684        public LispObject execute(LispObject[] args)
685        {
686            return jarray_ref(this, args, false);
687        }
688    };
689
690    private static final Primitive JARRAY_SET = new pf_jarray_set();
691    @DocString(name="jarray-set", args="java-array new-value &rest indices",
692    doc="Stores NEW-VALUE at the given index in JAVA-ARRAY.")
693    private static final class pf_jarray_set extends Primitive
694    {
695        pf_jarray_set()
696        {
697            super("jarray-set", PACKAGE_JAVA, true);
698        }
699
700        @Override
701        public LispObject execute(LispObject[] args)
702        {
703            if (args.length < 3)
704                error(new WrongNumberOfArgumentsException(this, 3, -1));
705            try {
706                Object a = args[0].javaInstance();
707                LispObject v = args[1];
708                for (int i = 2; i<args.length - 1; i++)
709                    a = Array.get(a, ((Integer)args[i].javaInstance()).intValue());
710                Object value = v.javaInstance();
711                int index = ((Integer)args[args.length - 1].javaInstance()).intValue();
712                if (value instanceof java.lang.Number
713                    && a.getClass().getComponentType().equals(Byte.TYPE)) {
714                    Array.setByte(a, index, ((java.lang.Number)value).byteValue());
715                } else {
716                    Array.set(a, index, value);
717                }
718                return v;
719            }
720            catch (Throwable t) { // no code -> no ControlTransfer
721                Symbol condition = getCondition(t.getClass());
722                if (condition == null)
723                    error(new JavaException(t));
724                else
725                    Symbol.SIGNAL.execute(
726                        condition,
727                        Keyword.CAUSE,
728                        JavaObject.getInstance(t),
729                        Keyword.FORMAT_CONTROL,
730                        new SimpleString(getMessage(t)));
731            }
732            // Not reached.
733            return NIL;
734        }
735    };
736
737    /**  Calls makeLispObject() to convert the result to an appropriate Lisp type. */
738    private static final Primitive JCALL = new pf_jcall();
739    @DocString(name="jcall", args="method-ref instance &rest args",
740    doc="Invokes the Java method METHOD-REF on INSTANCE with arguments ARGS," +
741        " coercing the result into a Lisp object, if possible.")
742    private static final class pf_jcall extends Primitive
743    {
744        pf_jcall()
745        {
746            super(Symbol.JCALL);
747        }
748
749        @Override
750        public LispObject execute(LispObject[] args)
751        {
752            return jcall(this, args, true);
753        }
754    };
755
756    /**
757     * Does no type conversion. The result of the call is simply wrapped in a
758     *   JavaObject.
759     */
760    private static final Primitive JCALL_RAW = new pf_jcall_raw();
761    @DocString(name="jcall-raw", args="method-ref instance &rest args",
762    doc="Invokes the Java method METHOD-REF on INSTANCE with arguments ARGS." +
763        " Does not attempt to coerce the result into a Lisp object.")
764    private static final class pf_jcall_raw extends Primitive
765    {
766        pf_jcall_raw()
767        {
768            super(Symbol.JCALL_RAW);
769        }
770
771        @Override
772        public LispObject execute(LispObject[] args)
773        {
774            return jcall(this, args, false);
775        }
776    };
777
778    private static final Primitive JRESOLVE_METHOD = new pf_jresolve_method();
779    @DocString(name="jresolve-method", args="method-name instance &rest args",
780    doc="Finds the most specific Java method METHOD-NAME on INSTANCE " +
781        "applicable to arguments ARGS. Returns NIL if no suitable method is " +
782        "found. The algorithm used for resolution is the same used by JCALL " +
783        "when it is called with a string as the first parameter (METHOD-REF).")
784    private static final class pf_jresolve_method extends Primitive {
785        pf_jresolve_method() {
786            super(Symbol.JRESOLVE_METHOD);
787        }
788
789        @Override
790        public LispObject execute(LispObject[] args) {
791            if (args.length < 2) {
792                error(new WrongNumberOfArgumentsException(this, 2, -1));
793            }
794            final LispObject methodArg = args[0];
795            final LispObject instanceArg = args[1];
796            final Object instance;
797            Class<?> intendedClass = null;
798            if (instanceArg instanceof AbstractString) {
799                instance = instanceArg.getStringValue();
800            } else if (instanceArg instanceof JavaObject) {
801                JavaObject jobj = ((JavaObject)instanceArg);
802                instance = jobj.getObject();
803                intendedClass = jobj.getIntendedClass();
804            } else {
805                instance = instanceArg.javaInstance();
806            }
807            if(instance == null) {
808                return program_error("JRESOLVE-METHOD: instance must not be null.");
809            }
810            String methodName = methodArg.getStringValue();
811            Object[] methodArgs = translateMethodArguments(args, 2);
812            Method method = findMethod(instance, intendedClass, methodName, methodArgs);
813            if (method != null) {
814                return JavaObject.getInstance(method);
815            } else if (instanceArg instanceof JavaObject) {
816                // Sometimes JavaObject.intendedClass has the default
817                // value java.lang.Object, so we try again to resolve
818                // the method using a dynamically requested value for
819                // java.lang.Class.
820                intendedClass = ((JavaObject)instanceArg).getObject().getClass();
821                method = findMethod(instance, intendedClass, methodName, methodArgs);
822            } else {
823                return NIL;
824            }
825            if (method != null) {
826                return JavaObject.getInstance(method);
827            } else {
828                return NIL;
829            }
830        }
831    };
832
833    static LispObject jcall(Primitive fun, LispObject[] args, boolean translate)
834
835    {
836        if (args.length < 2)
837            error(new WrongNumberOfArgumentsException(fun, 2, -1));
838        try {
839            final LispObject methodArg = args[0];
840            final LispObject instanceArg = args[1];
841            final Object instance;
842            Method method;
843            Object[] methodArgs;
844            Class<?> intendedClass = null;
845            if (instanceArg instanceof AbstractString) {
846                instance = instanceArg.getStringValue();
847            } else if (instanceArg instanceof JavaObject) {
848                JavaObject jobj = ((JavaObject)instanceArg);
849                instance = jobj.getObject();
850                intendedClass = jobj.getIntendedClass();
851            } else {
852                instance = instanceArg.javaInstance();
853            }
854            if(instance == null) {
855                throw new NullPointerException(); //Handled below
856            }
857            if (methodArg instanceof AbstractString) {
858                String methodName = methodArg.getStringValue();
859                methodArgs = translateMethodArguments(args, 2);
860                method = findMethod(instance, intendedClass, methodName, methodArgs);
861                if (method == null) {
862                    if (intendedClass == null) {
863                        String msg = MessageFormat.format("No instance method named {0} found for type {1}", methodName, instance.getClass().getName());
864                        throw new NoSuchMethodException(msg);
865                    }
866                    String classes = intendedClass.getName();
867                    Class<?> actualClass = instance.getClass();
868                    if(actualClass != intendedClass) {
869                        classes += " or " + actualClass.getName();
870                    }
871                    throw new NoSuchMethodException("No applicable method named " + methodName + " found in " + classes);
872                }
873            } else
874                method = (Method) JavaObject.getObject(methodArg);
875            Class<?>[] argTypes = (Class<?>[])method.getParameterTypes();
876      if(argTypes.length != args.length - 2) {
877    return error(new WrongNumberOfArgumentsException("Wrong number of arguments for " + method + ": expected " + argTypes.length + ", got " + (args.length - 2)));
878      }
879            methodArgs = new Object[argTypes.length];
880            for (int i = 2; i < args.length; i++) {
881                LispObject arg = args[i];
882                if (arg == NIL)
883                    methodArgs[i-2] = null;
884                else
885                    methodArgs[i-2] = arg.javaInstance(argTypes[i-2]);
886            }
887            if (!method.isAccessible()) {
888                 // Possible for static member classes: see #229
889                 if (Modifier.isPublic(method.getModifiers())) { 
890                    method.setAccessible(true);
891                 }
892      }
893            return JavaObject.getInstance(method.invoke(instance, methodArgs),
894                                          translate,
895                                          method.getReturnType());
896        }
897        catch (ControlTransfer t) {
898            throw t;
899        }
900        catch (Throwable t) { // ControlTransfer handled above
901            if (t instanceof InvocationTargetException)
902                t = t.getCause();
903            Symbol condition = getCondition(t.getClass());
904            if (condition == null)
905                error(new JavaException(t));
906            else
907                Symbol.SIGNAL.execute(
908                    condition,
909                    Keyword.CAUSE,
910                    JavaObject.getInstance(t),
911                    Keyword.FORMAT_CONTROL,
912                    new SimpleString(getMessage(t)));
913        }
914        // Not reached.
915        return null;
916    }
917
918    private static Object[] translateMethodArguments(LispObject[] args) {
919  return translateMethodArguments(args, 0);
920    }
921
922    private static Object[] translateMethodArguments(LispObject[] args, int offs) {
923  int argCount = args.length - offs;
924        Object[] javaArgs = new Object[argCount];
925        for (int i = 0; i < argCount; ++i) {
926            Object x = args[i + offs];
927            if (x == NIL) {
928                javaArgs[i] = null;
929            } else {
930                javaArgs[i] = ((LispObject) x).javaInstance();
931            }
932        }
933  return javaArgs;
934    }
935
936    private static Method findMethod(Method[] methods, String methodName, Object[] javaArgs) {
937        int argCount = javaArgs.length;
938        Method result = null;
939        for (int i = methods.length; i-- > 0;) {
940            Method method = methods[i];
941            if (!method.getName().equals(methodName)) {
942                continue;
943            }
944            if (method.getParameterTypes().length != argCount) {
945                continue;
946            }
947            Class<?>[] methodTypes = (Class<?>[]) method.getParameterTypes();
948            if (!isApplicableMethod(methodTypes, javaArgs)) {
949                continue;
950            }
951            if (result == null || isMoreSpecialized(methodTypes, result.getParameterTypes())) {
952                result = method;
953            }
954        }
955        return result;
956    }
957
958    private static Method findMethod(Object instance, Class<?> intendedClass, String methodName, Object[] methodArgs) {
959        if(intendedClass == null) {
960            intendedClass = instance.getClass();
961        }
962        Method method = findMethod(intendedClass, methodName, methodArgs);
963        Class actualClass = null;
964        if(method == null) {
965            actualClass = instance.getClass();
966            if(intendedClass != actualClass) { 
967                method = findMethod(actualClass, methodName, methodArgs);
968    if (method != null) {
969       if (isMethodCallableOnInstance(actualClass, method)) {
970          return method;
971       }
972    }
973            }
974        }
975        return method;
976    }
977   
978    private static boolean isMethodCallableOnInstance(Class instance, Method method) {
979       if (Modifier.isPublic(method.getModifiers())) {
980    return true;
981       }
982       if (instance.isMemberClass()) {
983    return isMethodCallableOnInstance(instance.getEnclosingClass(), method);
984       }
985       return false;
986    }
987
988    private static Method findMethod(Class<?> c, String methodName, Object[] javaArgs) {
989        Method[] methods = c.getMethods();
990        return findMethod(methods, methodName, javaArgs);
991    }
992
993    private static Method findMethod(Class<?> c, String methodName, LispObject[] args, int offset) {
994        Object[] javaArgs = translateMethodArguments(args, offset);
995        return findMethod(c, methodName, javaArgs);
996    }
997
998    private static Method findMethod(Method[] methods, String methodName, LispObject[] args, int offset) {
999        Object[] javaArgs = translateMethodArguments(args, offset);
1000        return findMethod(methods, methodName, javaArgs);
1001    }
1002
1003    static Constructor findConstructor(Class<?> c, LispObject[] args) throws NoSuchMethodException {
1004        int argCount = args.length - 1;
1005        Object[] javaArgs = translateMethodArguments(args, 1);
1006        Constructor[] ctors = c.getConstructors();
1007        Constructor result = null;
1008        for (int i = ctors.length; i-- > 0;) {
1009            Constructor ctor = ctors[i];
1010            if (ctor.getParameterTypes().length != argCount) {
1011                continue;
1012            }
1013            Class<?>[] methodTypes = (Class<?>[]) ctor.getParameterTypes();
1014            if (!isApplicableMethod(methodTypes, javaArgs)) {
1015                continue;
1016            }
1017            if (result == null || isMoreSpecialized(methodTypes, result.getParameterTypes())) {
1018                result = ctor;
1019            }
1020        }
1021        if (result == null) {
1022      StringBuilder sb = new StringBuilder(c.getSimpleName());
1023      sb.append('(');
1024      boolean first = true;
1025      for(Object o : javaArgs) {
1026    if(first) {
1027        first = false;
1028    } else {
1029        sb.append(", ");
1030    }
1031    if(o != null) {
1032        sb.append(o.getClass().getName());
1033    } else {
1034        sb.append("<null>");
1035    }
1036      }
1037      sb.append(')');
1038            throw new NoSuchMethodException(sb.toString());
1039        }
1040        return result;
1041    }
1042
1043    private static boolean isAssignable(Class<?> from, Class<?> to) {
1044        from = maybeBoxClass(from);
1045        to = maybeBoxClass(to);
1046        if (to.isAssignableFrom(from)) {
1047            return true;
1048        }
1049        if (Byte.class.equals(from)) {
1050            return Short.class.equals(to) || Integer.class.equals(to) || Long.class.equals(to) || Float.class.equals(to) || Double.class.equals(to);
1051        } else if (Short.class.equals(from) || Character.class.equals(from)) {
1052            return Integer.class.equals(to) || Long.class.equals(to) || Float.class.equals(to) || Double.class.equals(to);
1053        } else if (Integer.class.equals(from)) {
1054            return Long.class.equals(to) || Float.class.equals(to) || Double.class.equals(to);
1055        } else if (Long.class.equals(from)) {
1056            return Float.class.equals(to) || Double.class.equals(to);
1057        } else if (Float.class.equals(from)) {
1058            return Double.class.equals(to);
1059        }
1060        return false;
1061    }
1062
1063    private static boolean isApplicableMethod(Class<?>[] methodTypes,
1064            Object[] args) {
1065        for (int i = 0; i < methodTypes.length; ++i) {
1066            Class<?> methodType = methodTypes[i];
1067            Object arg = args[i];
1068            if (!isAssignable(arg.getClass(), methodType)) {
1069                return false;
1070            }
1071        }
1072        return true;
1073    }
1074
1075    private static boolean isMoreSpecialized(Class<?>[] xtypes, Class<?>[] ytypes) {
1076        for (int i = 0; i < xtypes.length; ++i) {
1077            Class<?> xtype = maybeBoxClass(xtypes[i]);
1078            Class<?> ytype = maybeBoxClass(ytypes[i]);
1079            if (xtype.equals(ytype)) {
1080                continue;
1081            }
1082            if (isAssignable(xtype, ytype)) {
1083                return true;
1084            }
1085        }
1086        return false;
1087    }
1088
1089    public static Class<?> maybeBoxClass(Class<?> clazz) {
1090  if(clazz.isPrimitive()) {
1091      return getBoxedClass(clazz);
1092  } else {
1093      return clazz;
1094  }
1095    }
1096   
1097    private static Class<?> getBoxedClass(Class<?> clazz) {
1098        if (clazz.equals(int.class)) {
1099            return Integer.class;
1100        } else if (clazz.equals(boolean.class)) {
1101            return Boolean.class;
1102        } else if (clazz.equals(byte.class)) {
1103            return Byte.class;
1104        } else if (clazz.equals(char.class)) {
1105            return Character.class;
1106        } else if (clazz.equals(long.class)) {
1107            return Long.class;
1108        } else if (clazz.equals(float.class)) {
1109            return Float.class;
1110        } else if (clazz.equals(double.class)) {
1111            return Double.class;
1112        } else if (clazz.equals(short.class)) {
1113            return Short.class;
1114        } else { // if (methodType.equals(void.class))
1115            return Void.class;
1116        }
1117    }
1118
1119    // DEPRECATED Remove MAKE-IMMEDIATE-OBJECT in abcl-0.29
1120    private static final Primitive MAKE_IMMEDIATE_OBJECT = new pf_make_immediate_object();
1121    @DocString(name="make-immediate-object", args="object &optional type",
1122    doc="Attempts to coerce a given Lisp object into a java-object of the\n"
1123      + "given type.  If type is not provided, works as jobject-lisp-value.\n"
1124      + "Currently, type may be :BOOLEAN, treating the object as a truth value,\n"
1125      + "or :REF, which returns Java null if NIL is provided.\n"
1126      + "\n"
1127      + "Deprecated.  Please use JAVA:+NULL+, JAVA:+TRUE+, and JAVA:+FALSE+ for\n"
1128      + "constructing wrapped primitive types, JAVA:JOBJECT-LISP-VALUE for converting a\n"
1129      + "JAVA:JAVA-OBJECT to a Lisp value, or JAVA:JNULL-REF-P to distinguish a wrapped\n"
1130      + "null JAVA-OBJECT from NIL.")
1131    private static final class pf_make_immediate_object extends Primitive
1132    {
1133        pf_make_immediate_object()
1134        {
1135            super("make-immediate-object", PACKAGE_JAVA, true);
1136        }
1137
1138        @Override
1139        public LispObject execute(LispObject[] args)
1140        {
1141            Symbol.WARN.getSymbolFunction()
1142                .execute(new SimpleString("JAVA:MAKE-IMMEDIATE-OBJECT is deprecated."));
1143            if (args.length < 1)
1144                error(new WrongNumberOfArgumentsException(this, 1, -1));
1145            LispObject object = args[0];
1146            if (args.length > 1) {
1147                LispObject type = args[1];
1148                if (type == Keyword.BOOLEAN) {
1149                    if (object == NIL)
1150                        return JavaObject.getInstance(Boolean.FALSE);
1151                    else
1152                        return JavaObject.getInstance(Boolean.TRUE);
1153                }
1154                if (type == Keyword.REF) {
1155                    if (object == NIL)
1156                        return JavaObject.getInstance(null);
1157                    else
1158                        error(new LispError("MAKE-IMMEDIATE-OBJECT: not implemented"));
1159                }
1160                // other special cases come here
1161            }
1162            return JavaObject.getInstance(object.javaInstance());
1163        }
1164    };
1165
1166    private static final Primitive JNULL_REF_P = new pf_jnull_ref_p();
1167    @DocString(name="jnull-ref-p", args="object",
1168    doc="Returns a non-NIL value when the JAVA-OBJECT `object` is `null`,\n"
1169            + "or signals a TYPE-ERROR condition if the object isn't of\n"
1170            + "the right type.")
1171    private static final class pf_jnull_ref_p extends Primitive
1172    {
1173        pf_jnull_ref_p()
1174        {
1175            super("jnull-ref-p", PACKAGE_JAVA, true);
1176        }
1177
1178        @Override
1179        public LispObject execute(LispObject ref)
1180        {
1181            if (ref instanceof JavaObject)
1182            {
1183                JavaObject jref = (JavaObject)ref;
1184                return (jref.javaInstance() == null) ? T : NIL;
1185            } else
1186                return Lisp.type_error(ref, Symbol.JAVA_OBJECT);
1187        }
1188    };
1189
1190
1191    private static final Primitive JAVA_OBJECT_P = new pf_java_object_p();
1192    @DocString(name="java-object-p", args="object",
1193    doc="Returns T if OBJECT is a JAVA-OBJECT.")
1194    private static final class pf_java_object_p extends Primitive
1195    {
1196        pf_java_object_p() 
1197        {
1198            super("java-object-p", PACKAGE_JAVA, true);
1199        }
1200
1201        @Override
1202        public LispObject execute(LispObject arg)
1203        {
1204            return (arg instanceof JavaObject) ? T : NIL;
1205        }
1206    };
1207
1208    private static final Primitive JOBJECT_LISP_VALUE = new pf_jobject_lisp_value();
1209    @DocString(name="jobject-lisp-value", args="java-object",
1210    doc="Attempts to coerce JAVA-OBJECT into a Lisp object.")
1211    private static final class pf_jobject_lisp_value extends Primitive
1212    {
1213        pf_jobject_lisp_value()
1214        {
1215            super("jobject-lisp-value", PACKAGE_JAVA, true, "java-object");
1216        }
1217
1218        @Override
1219        public LispObject execute(LispObject arg)
1220        {
1221            return JavaObject.getInstance(arg.javaInstance(), true);
1222        }
1223    };
1224
1225    private static final Primitive JCOERCE = new pf_jcoerce();
1226    @DocString(name="jcoerce", args="object intended-class",
1227    doc="Attempts to coerce OBJECT into a JavaObject of class INTENDED-CLASS." +
1228        "  Raises a TYPE-ERROR if no conversion is possible.")
1229    private static final class pf_jcoerce extends Primitive
1230    {
1231        pf_jcoerce()
1232        {
1233            super("jcoerce", PACKAGE_JAVA, true);
1234        }
1235
1236        @Override
1237        public LispObject execute(LispObject javaObject, LispObject intendedClass)
1238        {
1239      Object o = javaObject.javaInstance();
1240      Class<?> c = javaClass(intendedClass);
1241      try {
1242    return JavaObject.getInstance(o, c);
1243      } catch(ClassCastException e) {
1244          return type_error(javaObject, new SimpleString(c.getName()));
1245      }
1246        }
1247    };
1248
1249    private static final Primitive JRUN_EXCEPTION_PROTECTED = new pf_jrun_exception_protected();
1250    @DocString(name="jrun-exception-protected", args="closure",
1251    doc="Invokes the function CLOSURE and returns the result.  "+
1252        "Signals an error if stack or heap exhaustion occurs.")
1253    private static final class pf_jrun_exception_protected extends Primitive
1254    {
1255        pf_jrun_exception_protected()
1256        {
1257            super("jrun-exception-protected", PACKAGE_JAVA, true);
1258        }
1259
1260        @Override
1261        public LispObject execute(LispObject closure) {
1262            Function fun = checkFunction(closure);
1263
1264            try {
1265                return LispThread.currentThread().execute(closure);
1266            }
1267            catch (OutOfMemoryError oom) {
1268                return error(new StorageCondition("Out of memory " + oom.getMessage()));
1269            }
1270            catch (StackOverflowError oos) {
1271                oos.printStackTrace();
1272                return error(new StorageCondition("Stack overflow."));
1273            }
1274        }
1275    };
1276
1277    private static Class classForName(String className) {
1278  return classForName(className, JavaClassLoader.getPersistentInstance());
1279    }
1280
1281    private static Class classForName(String className, ClassLoader classLoader) {
1282        try {
1283            return Class.forName(className, true, classLoader);
1284        }
1285        catch (ClassNotFoundException e) {
1286      error(new LispError("Class not found: " + className));
1287      // Not reached.
1288      return null;
1289        }
1290    }
1291
1292    private static Class javaClass(LispObject obj) {
1293  return javaClass(obj, JavaClassLoader.getCurrentClassLoader());
1294    }
1295
1296    // Supports Java primitive types too.
1297    static Class javaClass(LispObject obj, ClassLoader classLoader)
1298    {
1299        if (obj instanceof AbstractString || obj instanceof Symbol) {
1300            String s = javaString(obj);
1301            if (s.equals("boolean"))
1302                return Boolean.TYPE;
1303            if (s.equals("byte"))
1304                return Byte.TYPE;
1305            if (s.equals("char"))
1306                return Character.TYPE;
1307            if (s.equals("short"))
1308                return Short.TYPE;
1309            if (s.equals("int"))
1310                return Integer.TYPE;
1311            if (s.equals("long"))
1312                return Long.TYPE;
1313            if (s.equals("float"))
1314                return Float.TYPE;
1315            if (s.equals("double"))
1316                return Double.TYPE;
1317            // Not a primitive Java type.
1318            Class c;
1319      c = classForName(s, classLoader);
1320            if (c == null)
1321                error(new LispError(s + " does not designate a Java class."));
1322
1323            return c;
1324        }
1325        // It's not a string, so it must be a JavaObject.
1326        final JavaObject javaObject;
1327        if (obj instanceof JavaObject) {
1328            javaObject = (JavaObject) obj;
1329        }
1330        else {
1331            type_error(obj, list(Symbol.OR, Symbol.STRING,
1332                                       Symbol.JAVA_OBJECT));
1333            // Not reached.
1334            return null;
1335        }
1336        final Object javaObjectgetObject = javaObject.getObject();
1337        if (javaObjectgetObject instanceof Class) {
1338            return (Class) javaObjectgetObject;
1339        }
1340            error(new LispError(obj.princToString() + " does not designate a Java class."));
1341            return null;
1342    }
1343
1344    static final String getMessage(Throwable t)
1345    {
1346        String message = t.getMessage();
1347        if (message == null || message.length() == 0)
1348            message = t.getClass().getName();
1349        return message;
1350    }
1351}
Note: See TracBrowser for help on using the repository browser.