8006168: ability to generate multi-type Java adapters
authorattila
Mon, 14 Jan 2013 16:00:55 +0100
changeset 16171 90dcd4fc42f0
parent 16170 9e3f4e0b519b
child 16172 25c8da53438f
8006168: ability to generate multi-type Java adapters Reviewed-by: lagergren, jlaskey
nashorn/src/jdk/nashorn/internal/objects/NativeJava.java
nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java
nashorn/src/jdk/nashorn/internal/runtime/linker/NashornPrimitiveLinker.java
nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties
nashorn/test/script/sandbox/javaextend.js
nashorn/test/script/sandbox/javaextend.js.EXPECTED
nashorn/test/src/jdk/nashorn/internal/test/models/DessertTopping.java
nashorn/test/src/jdk/nashorn/internal/test/models/DessertToppingFloorWaxDriver.java
nashorn/test/src/jdk/nashorn/internal/test/models/FloorWax.java
nashorn/test/src/jdk/nashorn/internal/test/models/Toothpaste.java
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java	Fri Jan 11 20:34:22 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java	Mon Jan 14 16:00:55 2013 +0100
@@ -145,7 +145,7 @@
      * this usage though, you can't use non-default constructors; the type must be either an interface, or must have a
      * protected or public no-arg constructor.
      * </p><p>
-     * You can also subclass non-abstract classes; for that you will need to use the {@link #extend(Object, Object)}
+     * You can also subclass non-abstract classes; for that you will need to use the {@link #extend(Object, Object...)}
      * method.
      * <h2>Accessing static members</h2>
      * Examples:
@@ -371,15 +371,29 @@
      * must be prepared to deal with all overloads.</li>
      * <li>You can't invoke {@code super.*()} from adapters for now.</li>
      * @param self not used
-     * @param type the original type. Must be a Java type object of class {@link StaticClass} representing either a
-     * public interface or a non-final public class with at least one public or protected constructor.
-     * @return a new {@link StaticClass} that represents the adapter for the original type.
+     * @param types the original types. The caller must pass at least one Java type object of class {@link StaticClass}
+     * representing either a public interface or a non-final public class with at least one public or protected
+     * constructor. If more than one type is specified, at most one can be a class and the rest have to be interfaces.
+     * Invoking the method twice with exactly the same types in the same order will return the same adapter
+     * class, any reordering of types or even addition or removal of redundant types (i.e. interfaces that other types
+     * in the list already implement/extend, or {@code java.lang.Object} in a list of types consisting purely of
+     * interfaces) will result in a different adapter class, even though those adapter classes are functionally
+     * identical; we deliberately don't want to incur the additional processing cost of canonicalizing type lists.
+     * @return a new {@link StaticClass} that represents the adapter for the original types.
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
-    public static Object extend(final Object self, final Object type) {
-        if(!(type instanceof StaticClass)) {
-            typeError(Global.instance(), "extend.expects.java.type");
+    public static Object extend(final Object self, final Object... types) {
+        if(types == null || types.length == 0) {
+            typeError(Global.instance(), "extend.expects.at.least.one.argument");
         }
-        return JavaAdapterFactory.getAdapterClassFor((StaticClass)type);
+        final Class<?>[] stypes = new Class<?>[types.length];
+        try {
+            for(int i = 0; i < types.length; ++i) {
+                stypes[i] = ((StaticClass)types[i]).getRepresentedClass();
+            }
+        } catch(final ClassCastException e) {
+            typeError(Global.instance(), "extend.expects.java.types");
+        }
+        return JavaAdapterFactory.getAdapterClassFor(stypes);
     }
 }
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java	Fri Jan 11 20:34:22 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java	Mon Jan 14 16:00:55 2013 +0100
@@ -57,9 +57,17 @@
 import java.security.ProtectionDomain;
 import java.security.SecureClassLoader;
 import java.security.SecureRandom;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 import jdk.internal.org.objectweb.asm.ClassWriter;
@@ -69,6 +77,7 @@
 import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
 import jdk.nashorn.internal.objects.NativeJava;
 import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.ECMAErrors;
 import jdk.nashorn.internal.runtime.ECMAException;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
@@ -78,43 +87,38 @@
 
 /**
  * A factory class that generates adapter classes. Adapter classes allow implementation of Java interfaces and
- * extending of Java classes from JavaScript. For every original Class object, exactly one adapter Class is generated
- * that either extends the original class or - if the original Class represents an interface - extends Object and
- * implements the interface represented by the original Class.
+ * extending of Java classes from JavaScript. For every combination of a superclass to extend and interfaces to
+ * implement (collectively: "original types"), exactly one adapter class is generated that extends the specified
+ * superclass and implements the specified interfaces.
  * </p><p>
  * The adapter class is generated in a new secure class loader that inherits Nashorn's protection domain, and has either
- * the original Class' class loader or the Nashorn's class loader as its parent - the parent class loader is chosen so
- * that both the original Class and the Nashorn core classes are visible from it (as the adapter will have constant pool
- * references to ScriptObject and ScriptFunction classes). In case neither candidate class loader has visibility into
- * the other set of classes, an error is thrown.
+ * one of the original types' class loader or the Nashorn's class loader as its parent - the parent class loader
+ * is chosen so that all the original types and the Nashorn core classes are visible from it (as the adapter will have
+ * constant pool references to ScriptObject and ScriptFunction classes). In case none of the candidate class loaders has
+ * visibility of all the required types, an error is thrown.
  * </p><p>
- * For every protected or public constructor in the extended class (which is either the original class, or Object when
- * an interface is implemented), the adapter class will have one or two public constructors (visibility of protected
- * constructors in the extended class is promoted to public). In every case, for every original constructor, a new
- * constructor taking a trailing ScriptObject argument preceded by original constructor arguments is present on the
- * adapter class. When such a constructor is invoked, the passed ScriptObject's member functions are used to implement
- * and/or override methods on the original class, dispatched by name. A single JavaScript function will act as the
- * implementation for all overloaded methods of the same name. When methods on an adapter instance are invoked, the
- * functions are invoked having the ScriptObject passed in the instance constructor as their "this". Subsequent changes
- * to the ScriptObject (reassignment or removal of its functions) are not reflected in the adapter instance; the method
- * implementations are bound to functions at constructor invocation time. {@code java.lang.Object} methods
- * {@code equals}, {@code hashCode}, and {@code toString} can also be overridden (from interface implementations too).
- * The only restriction is that since every JavaScript object already has a {@code toString} function through the
+ * For every protected or public constructor in the extended class, the adapter class will have one or two public
+ * constructors (visibility of protected constructors in the extended class is promoted to public). In every case, for
+ * every original constructor, a new constructor taking a trailing ScriptObject argument preceded by original
+ * constructor arguments is present on the adapter class. When such a constructor is invoked, the passed ScriptObject's
+ * member functions are used to implement and/or override methods on the original class, dispatched by name. A single
+ * JavaScript function will act as the implementation for all overloaded methods of the same name. When methods on an
+ * adapter instance are invoked, the functions are invoked having the ScriptObject passed in the instance constructor as
+ * their "this". Subsequent changes to the ScriptObject (reassignment or removal of its functions) are not reflected in
+ * the adapter instance; the method implementations are bound to functions at constructor invocation time.
+ * {@code java.lang.Object} methods {@code equals}, {@code hashCode}, and {@code toString} can also be overridden. The
+ * only restriction is that since every JavaScript object already has a {@code toString} function through the
  * {@code Object.prototype}, the {@code toString} in the adapter is only overridden if the passed ScriptObject has a
  * {@code toString} function as its own property, and not inherited from a prototype. All other adapter methods can be
  * implemented or overridden through a prototype-inherited function of the ScriptObject passed to the constructor too.
  * </p><p>
- * For abstract classes or interfaces that only have one abstract method, or have several of them, but all share the
+ * If the original types collectively have only one abstract method, or have several of them, but all share the
  * same name, an additional constructor is provided for every original constructor; this one takes a ScriptFunction as
  * its last argument preceded by original constructor arguments. This constructor will use the passed function as the
  * implementation for all abstract methods. For consistency, any concrete methods sharing the single abstract method
  * name will also be overridden by the function. When methods on the adapter instance are invoked, the ScriptFunction is
  * invoked with {@code null} as its "this".
  * </p><p>
- * If the superclass has a protected or public default constructor, then a generated constructor that only takes a
- * ScriptFunction is also implicitly used as an automatic conversion whenever a ScriptFunction is passed in an
- * invocation of any Java method that expects such SAM type.
- * </p><p>
  * For adapter methods that return values, all the JavaScript-to-Java conversions supported by Nashorn will be in effect
  * to coerce the JavaScript function return value to the expected Java return type.
  * </p><p>
@@ -125,7 +129,7 @@
  * to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>.
  * </p><p>
  * You normally don't use this class directly, but rather either create adapters from script using
- * {@link NativeJava#extend(Object, Object)}, using the {@code new} operator on abstract classes and interfaces (see
+ * {@link NativeJava#extend(Object, Object...)}, using the {@code new} operator on abstract classes and interfaces (see
  * {@link NativeJava#type(Object, Object)}), or implicitly when passing script functions to Java methods expecting SAM
  * types.
  * </p>
@@ -169,25 +173,28 @@
     private static final String ADAPTER_PACKAGE_PREFIX = "jdk/nashorn/internal/javaadapters/";
     // Class name suffix used to append to the adaptee class name, when it can be defined in the adaptee's package.
     private static final String ADAPTER_CLASS_NAME_SUFFIX = "$$NashornJavaAdapter";
+    private static final String JAVA_PACKAGE_PREFIX = "java/";
+    private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 238; //255 - 17; 17 is the maximum possible length for the global setter inner class suffix
 
-    private static final String JAVA_PACKAGE_PREFIX = "java/";
     private static final String INIT = "<init>";
     private static final String VOID_NOARG = Type.getMethodDescriptor(Type.VOID_TYPE);
     private static final String GLOBAL_FIELD_NAME = "global";
 
     /**
      * Contains various outcomes for attempting to generate an adapter class. These are stored in AdapterInfo instances.
-     * We have a successful outcome (adapter class was generated) and three possible error outcomes: a class is final,
-     * a class is not public, and the class has no public or protected constructor. We don't throw exceptions when we
-     * try to generate the adapter, but rather just record these error conditions as they are still useful as partial
-     * outcomes, as Nashorn's linker can still successfully check whether the class can be autoconverted from a script
-     * function even when it is not possible to generate an adapter for it.
+     * We have a successful outcome (adapter class was generated) and four possible error outcomes: superclass is final,
+     * superclass is not public, superclass has no public or protected constructor, more than one superclass was
+     * specified. We don't throw exceptions when we try to generate the adapter, but rather just record these error
+     * conditions as they are still useful as partial outcomes, as Nashorn's linker can still successfully check whether
+     * the class can be autoconverted from a script function even when it is not possible to generate an adapter for it.
      */
     private enum AdaptationOutcome {
         SUCCESS,
         ERROR_FINAL_CLASS,
         ERROR_NON_PUBLIC_CLASS,
-        ERROR_NO_ACCESSIBLE_CONSTRUCTOR
+        ERROR_NO_ACCESSIBLE_CONSTRUCTOR,
+        ERROR_MULTIPLE_SUPERCLASSES,
+        ERROR_NO_COMMON_LOADER
     }
 
     /**
@@ -198,27 +205,28 @@
     /**
      * A mapping from an original Class object to AdapterInfo representing the adapter for the class it represents.
      */
-    private static final ClassValue<AdapterInfo> ADAPTER_INFOS = new ClassValue<AdapterInfo>() {
+    private static final ClassValue<Map<List<Class<?>>, AdapterInfo>> ADAPTER_INFO_MAPS = new ClassValue<Map<List<Class<?>>, AdapterInfo>>() {
         @Override
-        protected AdapterInfo computeValue(final Class<?> type) {
-            return createAdapterInfo(type);
+        protected Map<List<Class<?>>, AdapterInfo> computeValue(final Class<?> type) {
+            return new HashMap<>();
         }
     };
 
     private static final Random random = new SecureRandom();
     private static final ProtectionDomain GENERATED_PROTECTION_DOMAIN = createGeneratedProtectionDomain();
 
-    // This is the supertype for our generated adapter. It's either Object if we're implementing an interface, or same
-    // as originalType if we're extending a class.
-    private final Class<?> superType;
+    // This is the superclass for our generated adapter.
+    private final Class<?> superClass;
     // Class loader used as the parent for the class loader we'll create to load the generated class. It will be a class
-    // loader that has the visibility of both the original type and of the Nashorn classes.
+    // loader that has the visibility of all original types (class to extend and interfaces to implement) and of the
+    // Nashorn classes.
     private final ClassLoader commonLoader;
 
-    // Binary name of the superType
-    private final String superTypeName;
+    // Binary name of the superClass
+    private final String superClassName;
     // Binary name of the generated class.
-    private final String generatedTypeName;
+    private final String generatedClassName;
+    // Binary name of the PrivilegedAction inner class that is used to
     private final String globalSetterClassName;
     private final Set<String> usedFieldNames = new HashSet<>();
     private final Set<String> abstractMethodNames = new HashSet<>();
@@ -232,10 +240,16 @@
     /**
      * Creates a factory that will produce the adapter type for the specified original type.
      * @param originalType the type for which this factory will generate the adapter type.
+     * @param definingClassAndLoader the class in whose ClassValue we'll store the generated adapter, and its class loader.
      * @throws AdaptationException if the adapter can not be generated for some reason.
      */
-    private JavaAdapterFactory(final Class<?> originalType) throws AdaptationException {
-        this.commonLoader = findCommonLoader(originalType);
+    private JavaAdapterFactory(final Class<?> superType, final List<Class<?>> interfaces, final ClassAndLoader definingClassAndLoader) throws AdaptationException {
+        assert superType != null && !superType.isInterface();
+        assert interfaces != null;
+        assert definingClassAndLoader != null;
+
+        this.superClass = superType;
+        this.commonLoader = findCommonLoader(definingClassAndLoader);
         cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) {
             @Override
             protected String getCommonSuperClass(final String type1, final String type2) {
@@ -244,39 +258,23 @@
                 return JavaAdapterFactory.this.getCommonSuperClass(type1, type2);
             }
         };
-        final String originalTypeName = Type.getInternalName(originalType);
-        final String[] interfaces;
-        final boolean isInterface = originalType.isInterface();
-        if (isInterface) {
-            superType = Object.class;
-            interfaces = new String[] { originalTypeName };
-        } else {
-            superType = originalType;
-            interfaces = null;
-        }
-        superTypeName = Type.getInternalName(superType);
-        final Package pkg = originalType.getPackage();
-        if (originalTypeName.startsWith(JAVA_PACKAGE_PREFIX) || pkg == null || pkg.isSealed()) {
-            // Can't define new classes in java.* packages
-            generatedTypeName = ADAPTER_PACKAGE_PREFIX + originalTypeName;
-        } else {
-            generatedTypeName = originalTypeName + ADAPTER_CLASS_NAME_SUFFIX;
-        }
+        superClassName = Type.getInternalName(superType);
+        generatedClassName = getGeneratedClassName(superType, interfaces);
+
         // Randomize the name of the privileged global setter, to make it non-feasible to find.
         final long l;
         synchronized(random) {
             l = random.nextLong();
         }
-        globalSetterClassName = generatedTypeName.concat("$" + Long.toHexString(l & Long.MAX_VALUE));
-        cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, generatedTypeName, null, superTypeName, interfaces);
+        // NOTE: they way this class name is calculated affects the value of MAX_GENERATED_TYPE_NAME_LENGTH constant. If
+        // you change the calculation of globalSetterClassName, adjust the constant too.
+        globalSetterClassName = generatedClassName.concat("$" + Long.toHexString(l & Long.MAX_VALUE));
+        cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, generatedClassName, null, superClassName, getInternalTypeNames(interfaces));
         cw.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd();
         usedFieldNames.add(GLOBAL_FIELD_NAME);
 
-        gatherMethods(originalType);
-        if (isInterface) {
-            // Add ability to override Object methods if implementing an interface
-            gatherMethods(Object.class);
-        }
+        gatherMethods(superType);
+        gatherMethods(interfaces);
         samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null;
         generateFields();
         generateConstructors();
@@ -285,6 +283,45 @@
         cw.visitEnd();
     }
 
+    private static String getGeneratedClassName(final Class<?> superType, final List<Class<?>> interfaces) {
+        // The class we use to primarily name our adapter is either the superclass, or if it is Object (meaning we're
+        // just implementing interfaces), then the first implemented interface.
+        final Class<?> namingType = superType == Object.class ? interfaces.get(0) : superType;
+        final Package pkg = namingType.getPackage();
+        final String namingTypeName = Type.getInternalName(namingType);
+        final StringBuilder buf = new StringBuilder();
+        if (namingTypeName.startsWith(JAVA_PACKAGE_PREFIX) || pkg == null || pkg.isSealed()) {
+            // Can't define new classes in java.* packages
+            buf.append(ADAPTER_PACKAGE_PREFIX).append(namingTypeName);
+        } else {
+            buf.append(namingTypeName).append(ADAPTER_CLASS_NAME_SUFFIX);
+        }
+        final Iterator<Class<?>> it = interfaces.iterator();
+        if(superType == Object.class && it.hasNext()) {
+            it.next(); // Skip first interface, it was used to primarily name the adapter
+        }
+        // Append interface names to the adapter name
+        while(it.hasNext()) {
+            buf.append("$$").append(it.next().getSimpleName());
+        }
+        return buf.toString().substring(0, Math.min(MAX_GENERATED_TYPE_NAME_LENGTH, buf.length()));
+    }
+
+    /**
+     * Given a list of class objects, return an array with their binary names. Used to generate the array of interface
+     * names to implement.
+     * @param classes the classes
+     * @return an array of names
+     */
+    private static String[] getInternalTypeNames(final List<Class<?>> classes) {
+        final int interfaceCount = classes.size();
+        final String[] interfaceNames = new String[interfaceCount];
+        for(int i = 0; i < interfaceCount; ++i) {
+            interfaceNames[i] = Type.getInternalName(classes.get(i));
+        }
+        return interfaceNames;
+    }
+
     /**
      * Utility method used by few other places in the code. Tests if the class has the abstract modifier and is not an
      * array class. For some reason, array classes have the abstract modifier set in HotSpot JVM, and we don't want to
@@ -297,29 +334,54 @@
     }
 
     /**
-     * Returns an adapter class for the specified original class. The adapter class extends/implements the original
-     * class/interface.
-     * @param originalClass the original class/interface to extend/implement.
+     * Returns an adapter class for the specified original types. The adapter class extends/implements the original
+     * class/interfaces.
+     * @param types the original types. The caller must pass at least one Java type representing either a public
+     * interface or a non-final public class with at least one public or protected constructor. If more than one type is
+     * specified, at most one can be a class and the rest have to be interfaces. The class can be in any position in the
+     * array. Invoking the method twice with exactly the same types in the same order will return the same adapter
+     * class, any reordering of types or even addition or removal of redundant types (i.e. interfaces that other types
+     * in the list already implement/extend, or {@code java.lang.Object} in a list of types consisting purely of
+     * interfaces) will result in a different adapter class, even though those adapter classes are functionally
+     * identical; we deliberately don't want to incur the additional processing cost of canonicalizing type lists.
      * @return an adapter class. See this class' documentation for details on the generated adapter class.
      * @throws ECMAException with a TypeError if the adapter class can not be generated because the original class is
      * final, non-public, or has no public or protected constructors.
      */
-    public static StaticClass getAdapterClassFor(final StaticClass originalClass) {
-        return getAdapterClassFor(originalClass.getRepresentedClass());
-    }
+    public static StaticClass getAdapterClassFor(final Class<?>[] types) {
+        assert types != null && types.length > 0;
+        final AdapterInfo adapterInfo = getAdapterInfo(types);
 
-    static StaticClass getAdapterClassFor(final Class<?> originalClass) {
-        final AdapterInfo adapterInfo = ADAPTER_INFOS.get(originalClass);
         final StaticClass clazz = adapterInfo.adapterClass;
         if (clazz != null) {
             return clazz;
         }
-        assert adapterInfo.adaptationOutcome != AdaptationOutcome.SUCCESS;
-        typeError(Context.getGlobal(), "extend." + adapterInfo.adaptationOutcome, originalClass.getName());
+        adapterInfo.adaptationOutcome.typeError();
 
         throw new AssertionError();
     }
 
+    private static AdapterInfo getAdapterInfo(final Class<?>[] types) {
+        final ClassAndLoader definingClassAndLoader = getDefiningClassAndLoader(types);
+
+        final Map<List<Class<?>>, AdapterInfo> adapterInfoMap = ADAPTER_INFO_MAPS.get(definingClassAndLoader.clazz);
+        final List<Class<?>> typeList = types.length == 1 ? getSingletonClassList(types[0]) : Arrays.asList(types.clone());
+        AdapterInfo adapterInfo;
+        synchronized(adapterInfoMap) {
+            adapterInfo = adapterInfoMap.get(typeList);
+            if(adapterInfo == null) {
+                adapterInfo = createAdapterInfo(types, definingClassAndLoader);
+                adapterInfoMap.put(typeList, adapterInfo);
+            }
+        }
+        return adapterInfo;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private static List<Class<?>> getSingletonClassList(final Class<?> clazz) {
+        return (List)Collections.singletonList(clazz);
+    }
+
     /**
      * Returns whether an instance of the specified class/interface can be generated from a ScriptFunction. Returns true
      * iff: the adapter for the class/interface can be created, it is abstract (this includes interfaces), it has at
@@ -330,7 +392,7 @@
      * @return true iff an instance of the specified class/interface can be generated from a ScriptFunction.
      */
     static boolean isAutoConvertibleFromFunction(final Class<?> clazz) {
-        return ADAPTER_INFOS.get(clazz).autoConvertibleFromFunction;
+        return getAdapterInfo(new Class<?>[] { clazz }).autoConvertibleFromFunction;
     }
 
     /**
@@ -346,7 +408,7 @@
      * @throws Exception if anything goes wrong
      */
     public static MethodHandle getConstructor(final Class<?> sourceType, final Class<?> targetType) throws Exception {
-        final StaticClass adapterClass = getAdapterClassFor(targetType);
+        final StaticClass adapterClass = getAdapterClassFor(new Class<?>[] { targetType });
         return MH.bindTo(Bootstrap.getLinkerServices().getGuardedInvocation(new LinkRequestImpl(NashornCallSiteDescriptor.get(
                 "dyn:new", MethodType.methodType(targetType, StaticClass.class, sourceType), 0), false,
                 adapterClass, null)).getInvocation(), adapterClass);
@@ -358,7 +420,7 @@
      * @return the generated adapter class
      */
     private Class<?> generateClass() {
-        final String binaryName = generatedTypeName.replace('/', '.');
+        final String binaryName = generatedClassName.replace('/', '.');
         try {
             return Class.forName(binaryName, true, createClassLoader(commonLoader, binaryName, cw.toByteArray(),
                     globalSetterClassName.replace('/', '.')));
@@ -467,7 +529,7 @@
 
     private void generateConstructors() throws AdaptationException {
         boolean gotCtor = false;
-        for (final Constructor<?> ctor: superType.getDeclaredConstructors()) {
+        for (final Constructor<?> ctor: superClass.getDeclaredConstructors()) {
             final int modifier = ctor.getModifiers();
             if((modifier & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
                 generateConstructor(ctor);
@@ -475,7 +537,7 @@
             }
         }
         if(!gotCtor) {
-            throw new AdaptationException(AdaptationOutcome.ERROR_NO_ACCESSIBLE_CONSTRUCTOR);
+            throw new AdaptationException(AdaptationOutcome.ERROR_NO_ACCESSIBLE_CONSTRUCTOR, superClass.getCanonicalName());
         }
     }
 
@@ -548,7 +610,7 @@
             mv.load(offset, argType);
             offset += argType.getSize();
         }
-        mv.invokespecial(superTypeName, INIT, originalCtorType.getDescriptor());
+        mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor());
 
         // Get a descriptor to the appropriate "JavaAdapterFactory.getHandle" method.
         final String getHandleDescriptor = fromFunction ? GET_HANDLE_FUNCTION_DESCRIPTOR : GET_HANDLE_OBJECT_DESCRIPTOR;
@@ -570,7 +632,7 @@
                 mv.iconst(mi.method.isVarArgs() ? 1 : 0);
                 mv.invokestatic(THIS_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor);
             }
-            mv.putfield(generatedTypeName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
+            mv.putfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
         }
 
         // Assign "this.global = Context.getGlobal()"
@@ -579,7 +641,7 @@
         mv.dup();
         mv.invokevirtual(OBJECT_TYPE_NAME, "getClass", GET_CLASS_METHOD_DESCRIPTOR); // check against null Context
         mv.pop();
-        mv.putfield(generatedTypeName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+        mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
 
         // Wrap up
         mv.visitInsn(RETURN);
@@ -747,7 +809,7 @@
 
         // Get the method handle
         mv.visitVarInsn(ALOAD, 0);
-        mv.getfield(generatedTypeName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
+        mv.getfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
         mv.visitInsn(DUP); // It'll remain on the stack all the way until the invocation
         // Check if the method handle is null
         mv.visitJumpInsn(IFNONNULL, methodHandleNotNull);
@@ -765,7 +827,7 @@
                 mv.load(nextParam, t);
                 nextParam += t.getSize();
             }
-            mv.invokespecial(superTypeName, name, methodDesc);
+            mv.invokespecial(superClassName, name, methodDesc);
             mv.areturn(returnType);
         }
 
@@ -872,7 +934,7 @@
 
     private void loadGlobalOnStack(final InstructionAdapter mv) {
         mv.visitVarInsn(ALOAD, 0);
-        mv.getfield(generatedTypeName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+        mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
     }
 
     private static boolean isThrowableDeclared(final Class<?>[] exceptions) {
@@ -909,7 +971,7 @@
                         if (Modifier.isAbstract(m)) {
                             abstractMethodNames.add(mi.getName());
                         }
-                        mi.setIsCanonical (usedFieldNames);
+                        mi.setIsCanonical(usedFieldNames);
                     }
                 }
             }
@@ -920,9 +982,9 @@
         // superclass. For interfaces, we used Class.getMethods(), as we're only interested in public ones there, and
         // getMethods() does provide those declared in a superinterface.
         if (!type.isInterface()) {
-            final Class<?> superClass = type.getSuperclass();
-            if (superClass != null) {
-                gatherMethods(superClass);
+            final Class<?> superType = type.getSuperclass();
+            if (superType != null) {
+                gatherMethods(superType);
             }
             for (final Class<?> itf: type.getInterfaces()) {
                 gatherMethods(itf);
@@ -930,6 +992,12 @@
         }
     }
 
+    private void gatherMethods(final List<Class<?>> classes) {
+        for(final Class<?> c: classes) {
+            gatherMethods(c);
+        }
+    }
+
     /**
      * Creates a collection of methods that are not final, but we still never allow them to be overridden in adapters,
      * as explicitly declaring them automatically is a bad idea. Currently, this means {@code Object.finalize()} and
@@ -965,18 +1033,43 @@
     private static class AdapterInfo {
         final StaticClass adapterClass;
         final boolean autoConvertibleFromFunction;
-        final AdaptationOutcome adaptationOutcome;
+        final AnnotatedAdaptationOutcome adaptationOutcome;
 
         AdapterInfo(final StaticClass adapterClass, final boolean autoConvertibleFromFunction) {
             this.adapterClass = adapterClass;
             this.autoConvertibleFromFunction = autoConvertibleFromFunction;
-            this.adaptationOutcome = AdaptationOutcome.SUCCESS;
+            this.adaptationOutcome = AnnotatedAdaptationOutcome.SUCCESS;
+        }
+
+        AdapterInfo(final AdaptationOutcome outcome, final String classList) {
+            this(new AnnotatedAdaptationOutcome(outcome, classList));
         }
 
-        AdapterInfo(final AdaptationOutcome outcome) {
+        AdapterInfo(final AnnotatedAdaptationOutcome adaptationOutcome) {
             this.adapterClass = null;
             this.autoConvertibleFromFunction = false;
-            this.adaptationOutcome = outcome;
+            this.adaptationOutcome = adaptationOutcome;
+        }
+    }
+
+    /**
+     * An adaptation outcome accompanied with a name of a class (or a list of multiple class names) that are the reason
+     * an adapter could not be generated.
+     */
+    private static class AnnotatedAdaptationOutcome {
+        static final AnnotatedAdaptationOutcome SUCCESS = new AnnotatedAdaptationOutcome(AdaptationOutcome.SUCCESS, "");
+
+        private final AdaptationOutcome adaptationOutcome;
+        private final String classList;
+
+        AnnotatedAdaptationOutcome(final AdaptationOutcome adaptationOutcome, final String classList) {
+            this.adaptationOutcome = adaptationOutcome;
+            this.classList = classList;
+        }
+
+        void typeError() {
+            assert adaptationOutcome != AdaptationOutcome.SUCCESS;
+            ECMAErrors.typeError(Context.getGlobal(), "extend." + adaptationOutcome, classList);
         }
     }
 
@@ -985,19 +1078,32 @@
      * @param type the class for which the adapter is created
      * @return the adapter info for the class.
      */
-    private static AdapterInfo createAdapterInfo(final Class<?> type) {
-        final int mod = type.getModifiers();
-        if (Modifier.isFinal(mod)) {
-            return new AdapterInfo(AdaptationOutcome.ERROR_FINAL_CLASS);
+    private static AdapterInfo createAdapterInfo(final Class<?>[] types, final ClassAndLoader definingClassAndLoader) {
+        Class<?> superClass = null;
+        final List<Class<?>> interfaces = new ArrayList<>(types.length);
+        for(final Class<?> t: types) {
+            final int mod = t.getModifiers();
+            if(!t.isInterface()) {
+                if(superClass != null) {
+                    return new AdapterInfo(AdaptationOutcome.ERROR_MULTIPLE_SUPERCLASSES, t.getCanonicalName() + " and " + superClass.getCanonicalName());
+                }
+                if (Modifier.isFinal(mod)) {
+                    return new AdapterInfo(AdaptationOutcome.ERROR_FINAL_CLASS, t.getCanonicalName());
+                }
+                superClass = t;
+            } else {
+                interfaces.add(t);
+            }
+            if(!Modifier.isPublic(mod)) {
+                return new AdapterInfo(AdaptationOutcome.ERROR_NON_PUBLIC_CLASS, t.getCanonicalName());
+            }
         }
-        if (!Modifier.isPublic(mod)) {
-            return new AdapterInfo(AdaptationOutcome.ERROR_NON_PUBLIC_CLASS);
-        }
+        final Class<?> effectiveSuperClass = superClass == null ? Object.class : superClass;
         return AccessController.doPrivileged(new PrivilegedAction<AdapterInfo>() {
             @Override
             public AdapterInfo run() {
                 try {
-                    final JavaAdapterFactory factory = new JavaAdapterFactory(type);
+                    final JavaAdapterFactory factory = new JavaAdapterFactory(effectiveSuperClass, interfaces, definingClassAndLoader);
                     return new AdapterInfo(StaticClass.forClass(factory.generateClass()),
                             factory.isAutoConvertibleFromFunction());
                 } catch (final AdaptationException e) {
@@ -1009,9 +1115,9 @@
 
     @SuppressWarnings("serial")
     private static class AdaptationException extends Exception {
-        private final AdaptationOutcome outcome;
-        AdaptationException(final AdaptationOutcome outcome) {
-            this.outcome = outcome;
+        private final AnnotatedAdaptationOutcome outcome;
+        AdaptationException(final AdaptationOutcome outcome, final String classList) {
+            this.outcome = new AnnotatedAdaptationOutcome(outcome, classList);
         }
     }
 
@@ -1040,24 +1146,25 @@
     }
 
     /**
-     * Finds a class loader that sees both the specified class and Nashorn classes.
-     * @param clazz the class that needs to be visible from the found class loader.
+     * Choose between the passed class loader and the class loader that defines the ScriptObject class, based on which
+     * of the two can see the classes in both.
+     * @param classAndLoader the loader and a representative class from it that will be used to add the generated
+     * adapter to its ADAPTER_INFO_MAPS.
      * @return the class loader that sees both the specified class and Nashorn classes.
      * @throws IllegalStateException if no such class loader is found.
      */
-    private static ClassLoader findCommonLoader(final Class<?> clazz) {
-        final ClassLoader clazzLoader = clazz.getClassLoader();
-        if (canSeeClass(clazzLoader, ScriptObject.class)) {
-            return clazzLoader;
+    private static ClassLoader findCommonLoader(final ClassAndLoader classAndLoader) throws AdaptationException {
+        final ClassLoader loader = classAndLoader.getLoader();
+        if (canSeeClass(loader, ScriptObject.class)) {
+            return loader;
         }
 
         final ClassLoader nashornLoader = ScriptObject.class.getClassLoader();
-        if(canSeeClass(nashornLoader, clazz)) {
+        if(canSeeClass(nashornLoader, classAndLoader.clazz)) {
             return nashornLoader;
         }
 
-        throw new IllegalStateException("Can't find a common class loader for ScriptObject and " +
-                    clazz.getName());
+        throw new AdaptationException(AdaptationOutcome.ERROR_NO_COMMON_LOADER, classAndLoader.clazz.getCanonicalName());
     }
 
     private static boolean canSeeClass(final ClassLoader cl, final Class<?> clazz) {
@@ -1067,4 +1174,141 @@
             return false;
         }
     }
+
+    /**
+     * Given a list of types that define the superclass/interfaces for an adapter class, returns a single type from the
+     * list that will be used to attach the adapter to its ClassValue. The first type in the array that is defined in a
+     * class loader that can also see all other types is returned. If there is no such loader, an exception is thrown.
+     * @param types the input types
+     * @return the first type from the array that is defined in a class loader that can also see all other types.
+     */
+    private static ClassAndLoader getDefiningClassAndLoader(final Class<?>[] types) {
+        // Short circuit the cheap case
+        if(types.length == 1) {
+            return new ClassAndLoader(types[0], false);
+        }
+
+        return AccessController.doPrivileged(new PrivilegedAction<ClassAndLoader>() {
+            @Override
+            public ClassAndLoader run() {
+                return getDefiningClassAndLoaderPrivileged(types);
+            }
+        });
+    }
+
+    private static ClassAndLoader getDefiningClassAndLoaderPrivileged(final Class<?>[] types) {
+        final Collection<ClassAndLoader> maximumVisibilityLoaders = getMaximumVisibilityLoaders(types);
+
+        final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator();
+        if(maximumVisibilityLoaders.size() == 1) {
+            // Fortunate case - single maximally specific class loader; return its representative class.
+            return it.next();
+        }
+
+        // Ambiguity; throw an error.
+        assert maximumVisibilityLoaders.size() > 1; // basically, can't be zero
+        final StringBuilder b = new StringBuilder();
+        b.append(it.next().clazz.getCanonicalName());
+        while(it.hasNext()) {
+            b.append(", ").append(it.next().clazz.getCanonicalName());
+        }
+        typeError(Context.getGlobal(), "extend.ambiguous.defining.class", b.toString());
+        throw new AssertionError(); // never reached
+    }
+
+    /**
+     * Given an array of types, return a subset of their class loaders that are maximal according to the
+     * "can see other loaders' classes" relation, which is presumed to be a partial ordering.
+     * @param types types
+     * @return a collection of maximum visibility class loaders. It is guaranteed to have at least one element.
+     */
+    private static Collection<ClassAndLoader> getMaximumVisibilityLoaders(final Class<?>[] types) {
+        final List<ClassAndLoader> maximumVisibilityLoaders = new LinkedList<>();
+        outer:  for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) {
+            final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator();
+            while(it.hasNext()) {
+                final ClassAndLoader existingMax = it.next();
+                final boolean candidateSeesExisting = canSeeClass(maxCandidate.getRetrievedLoader(), existingMax.clazz);
+                final boolean exitingSeesCandidate = canSeeClass(existingMax.getRetrievedLoader(), maxCandidate.clazz);
+                if(candidateSeesExisting) {
+                    if(!exitingSeesCandidate) {
+                        // The candidate sees the the existing maximum, so drop the existing one as it's no longer maximal.
+                        it.remove();
+                    }
+                    // NOTE: there's also the anomalous case where both loaders see each other. Not sure what to do
+                    // about that one, as two distinct class loaders both seeing each other's classes is weird and
+                    // violates the assumption that the relation "sees others' classes" is a partial ordering. We'll
+                    // just not do anything, and treat them as incomparable; hopefully some later class loader that
+                    // comes along can eliminate both of them, if it can not, we'll end up with ambiguity anyway and
+                    // throw an error at the end.
+                } else if(exitingSeesCandidate) {
+                    // Existing sees the candidate, so drop the candidate.
+                    continue outer;
+                }
+            }
+            // If we get here, no existing maximum visibility loader could see the candidate, so the candidate is a new
+            // maximum.
+            maximumVisibilityLoaders.add(maxCandidate);
+        }
+        return maximumVisibilityLoaders;
+    }
+
+    private static Collection<ClassAndLoader> getClassLoadersForTypes(final Class<?>[] types) {
+        final Map<ClassAndLoader, ClassAndLoader> classesAndLoaders = new LinkedHashMap<>();
+        for(final Class<?> c: types) {
+            final ClassAndLoader cl = new ClassAndLoader(c, true);
+            if(!classesAndLoaders.containsKey(cl)) {
+                classesAndLoaders.put(cl, cl);
+            }
+        }
+        return classesAndLoaders.keySet();
+    }
+
+    /**
+     * A tuple of a class loader and a single class representative of the classes that can be loaded through it. Its
+     * equals/hashCode is defined in terms of the identity of the class loader.
+     */
+    private static final class ClassAndLoader {
+        private final Class<?> clazz;
+        // Don't access this directly; most of the time, use getRetrievedLoader(), or if you know what you're doing,
+        // getLoader().
+        private ClassLoader loader;
+        // We have mild affinity against eagerly retrieving the loader, as we need to do it in a privileged block. For
+        // the most basic case of looking up an already-generated adapter info for a single type, we avoid it.
+        private boolean loaderRetrieved;
+
+        ClassAndLoader(final Class<?> clazz, final boolean retrieveLoader) {
+            this.clazz = clazz;
+            if(retrieveLoader) {
+                retrieveLoader();
+            }
+        }
+
+        ClassLoader getLoader() {
+            if(!loaderRetrieved) {
+                retrieveLoader();
+            }
+            return getRetrievedLoader();
+        }
+
+        ClassLoader getRetrievedLoader() {
+            assert loaderRetrieved;
+            return loader;
+        }
+
+        private void retrieveLoader() {
+            loader = clazz.getClassLoader();
+            loaderRetrieved = true;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            return obj instanceof ClassAndLoader && ((ClassAndLoader)obj).getRetrievedLoader() == getRetrievedLoader();
+        }
+
+        @Override
+        public int hashCode() {
+            return System.identityHashCode(getRetrievedLoader());
+        }
+    }
 }
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornPrimitiveLinker.java	Fri Jan 11 20:34:22 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornPrimitiveLinker.java	Mon Jan 14 16:00:55 2013 +0100
@@ -94,7 +94,7 @@
                 if (JavaAdapterFactory.isAbstractClass(receiverClass)) {
                     // Change this link request into a link request on the adapter class.
                     final Object[] args = request.getArguments();
-                    args[0] = JavaAdapterFactory.getAdapterClassFor(receiverClass);
+                    args[0] = JavaAdapterFactory.getAdapterClassFor(new Class<?>[] { receiverClass });
                     final LinkRequest adapterRequest = request.replaceArguments(request.getCallSiteDescriptor(), args);
                     final GuardedInvocation gi = checkNullConstructor(
                             staticClassLinker.getGuardedInvocation(adapterRequest, linkerServices), receiverClass);
--- a/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties	Fri Jan 11 20:34:22 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties	Mon Jan 14 16:00:55 2013 +0100
@@ -110,10 +110,14 @@
 type.error.cant.convert.to.java.string=Cannot convert object of type {0} to a Java argument of string type
 type.error.cant.convert.to.java.number=Cannot convert object of type {0} to a Java argument of number type
 type.error.cant.convert.to.javascript.array=Can only convert Java arrays and lists to JavaScript arrays. Can't convert object of type {0}.
-type.error.extend.expects.java.type=Java.extend needs a Java type as its argument.
+type.error.extend.expects.at.least.one.argument=Java.extend needs at least one argument.
+type.error.extend.expects.java.types=Java.extend needs Java types as its arguments.
+type.error.extend.ambiguous.defining.class=There is no class loader that can see all of {0} at once.
 type.error.extend.ERROR_FINAL_CLASS=Can not extend final class {0}.
 type.error.extend.ERROR_NON_PUBLIC_CLASS=Can not extend/implement non-public class/interface {0}.
 type.error.extend.ERROR_NO_ACCESSIBLE_CONSTRUCTOR=Can not extend class {0} as it has no public or protected constructors.
+type.error.extend.ERROR_MULTIPLE_SUPERCLASSES=Can not extend multiple classes {0}. At most one of the specified types can be a class, the rest must all be interfaces.
+type.error.extend.ERROR_NO_COMMON_LOADER=Can not find a common class loader for ScriptObject and {0}.
 type.error.no.constructor.matches.args=Can not construct {0} with the passed arguments; they do not match any of its constructor signatures.
 type.error.no.method.matches.args=Can not invoke method {0} with the passed arguments; they do not match any of its method signatures.
 type.error.method.not.constructor=Java method {0} can't be used as a constructor.
--- a/nashorn/test/script/sandbox/javaextend.js	Fri Jan 11 20:34:22 2013 +0530
+++ b/nashorn/test/script/sandbox/javaextend.js	Mon Jan 14 16:00:55 2013 +0100
@@ -51,6 +51,13 @@
     print(e)
 }
 
+// Can't extend two classes
+try {
+    Java.extend(java.lang.Thread,java.lang.Number)
+} catch(e) {
+    print(e)
+}
+
 // Make sure we can implement interfaces from the unnamed package
 var c = new (Java.extend(Java.type("UnnamedPackageTestCallback")))() { call: function(s) { return s + s } }
 print(c.call("abcd"))
@@ -104,3 +111,22 @@
 // Do the same thing with proprietary syntax and object literal
 var cwa2 = new (model("ConstructorWithArgument"))("cwa2-token") { doSomething: function() { print("cwa2-" + cwa2.token ) } }
 cwa2.doSomething()
+
+// Implement two interfaces
+var desertToppingAndFloorWax = new (Java.extend(model("DessertTopping"), model("FloorWax"))) {
+    pourOnDessert: function() { print("Glop; IM IN UR DESSERT NOW") },
+    shineUpTheFloor: function() { print("The floor sure is shining!") }
+}
+var dtfwDriver = new (model("DessertToppingFloorWaxDriver"))
+dtfwDriver.decorateDessert(desertToppingAndFloorWax)
+dtfwDriver.waxFloor(desertToppingAndFloorWax)
+
+// Extend a class and implement two interfaces. For additional measure, put the class in between the two interfaces
+var desertToppingFloorWaxAndToothpaste = new (Java.extend(model("DessertTopping"), model("Toothpaste"), model("FloorWax"))) {
+    pourOnDessert: function() { print("Yum") },
+    shineUpTheFloor: function() { print("Scrub, scrub, scrub") },
+    applyToBrushImpl: function() { print("It's a dessert topping! It's a floor wax! It's a toothpaste!") }
+}
+dtfwDriver.decorateDessert(desertToppingFloorWaxAndToothpaste)
+dtfwDriver.waxFloor(desertToppingFloorWaxAndToothpaste)
+desertToppingFloorWaxAndToothpaste.applyToBrush();
--- a/nashorn/test/script/sandbox/javaextend.js.EXPECTED	Fri Jan 11 20:34:22 2013 +0530
+++ b/nashorn/test/script/sandbox/javaextend.js.EXPECTED	Mon Jan 14 16:00:55 2013 +0100
@@ -1,6 +1,7 @@
 TypeError: Can not extend final class jdk.nashorn.internal.test.models.FinalClass.
 TypeError: Can not extend class jdk.nashorn.internal.test.models.NoAccessibleConstructorClass as it has no public or protected constructors.
 TypeError: Can not extend/implement non-public class/interface jdk.nashorn.internal.test.models.NonPublicClass.
+TypeError: Can not extend multiple classes java.lang.Number and java.lang.Thread. At most one of the specified types can be a class, the rest must all be interfaces.
 abcdabcd
 run-object
 run-fn
@@ -18,3 +19,8 @@
 oo-proto-overridden-equals  : true
 cwa-token
 cwa2-cwa2-token
+Glop; IM IN UR DESSERT NOW
+The floor sure is shining!
+Yum
+Scrub, scrub, scrub
+It's a dessert topping! It's a floor wax! It's a toothpaste!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/src/jdk/nashorn/internal/test/models/DessertTopping.java	Mon Jan 14 16:00:55 2013 +0100
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.test.models;
+
+public interface DessertTopping {
+    public String pourOnDessert();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/src/jdk/nashorn/internal/test/models/DessertToppingFloorWaxDriver.java	Mon Jan 14 16:00:55 2013 +0100
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.test.models;
+
+public class DessertToppingFloorWaxDriver {
+    public void decorateDessert(DessertTopping dt) {
+        dt.pourOnDessert();
+    }
+
+    public void waxFloor(FloorWax fw) {
+        fw.shineUpTheFloor();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/src/jdk/nashorn/internal/test/models/FloorWax.java	Mon Jan 14 16:00:55 2013 +0100
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.test.models;
+
+public interface FloorWax {
+    public String shineUpTheFloor();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/src/jdk/nashorn/internal/test/models/Toothpaste.java	Mon Jan 14 16:00:55 2013 +0100
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.test.models;
+
+public abstract class Toothpaste {
+    public void applyToBrush() {
+        applyToBrushImpl();
+    }
+
+    protected abstract void applyToBrushImpl();
+}