1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.runtime;
  27 
  28 import java.lang.invoke.MethodHandle;
  29 import java.lang.invoke.MethodHandles;
  30 import java.util.concurrent.Callable;
  31 
  32 import jdk.nashorn.internal.codegen.CompilerConstants;
  33 import jdk.nashorn.internal.lookup.Lookup;
  34 import jdk.nashorn.internal.runtime.linker.Bootstrap;
  35 
  36 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
  37 import jdk.nashorn.internal.objects.Global;
  38 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  39 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
  40 
  41 /**
  42  * Property with user defined getters/setters. Actual getter and setter
  43  * functions are stored in underlying ScriptObject. Only the 'slot' info is
  44  * stored in the property.
  45  *
  46  * The slots here denote either ScriptObject embed field number or spill
  47  * array index. For spill array index, we use slot value of
  48  * (index + ScriptObject.embedSize). See also ScriptObject.getEmbedOrSpill
  49  * method. Negative slot value means that the corresponding getter or setter
  50  * is null. Note that always two slots are allocated in ScriptObject - but
  51  * negative (less by 1) slot number is stored for null getter or setter.
  52  * This is done so that when the property is redefined with a different
  53  * getter and setter (say, both non-null), we'll have spill slots to store
  54  * those. When a slot is negative, (-slot - 1) is the embed/spill index.
  55  */
  56 public final class UserAccessorProperty extends Property {
  57 
  58     /** User defined getter function slot. */
  59     private final int getterSlot;
  60 
  61     /** User defined setter function slot. */
  62     private final int setterSlot;
  63 
  64     /** Getter method handle */
  65     private final static CompilerConstants.Call USER_ACCESSOR_GETTER = staticCall(MethodHandles.lookup(), UserAccessorProperty.class,
  66             "userAccessorGetter", Object.class, ScriptObject.class, int.class, Object.class);
  67 
  68     /** Setter method handle */
  69     private final static CompilerConstants.Call USER_ACCESSOR_SETTER = staticCall(MethodHandles.lookup(), UserAccessorProperty.class,
  70             "userAccessorSetter", void.class, ScriptObject.class, int.class, String.class, Object.class, Object.class);
  71 
  72     /** Dynamic invoker for getter */
  73     private static final Object INVOKE_UA_GETTER = new Object();
  74 
  75     private static MethodHandle getINVOKE_UA_GETTER() {
  76 
  77         return Context.getGlobal().getDynamicInvoker(INVOKE_UA_GETTER,
  78                 new Callable<MethodHandle>() {
  79                     @Override
  80                     public MethodHandle call() {
  81                         return Bootstrap.createDynamicInvoker("dyn:call", Object.class,
  82                             Object.class, Object.class);
  83                     }
  84                 });
  85     }
  86 
  87     /** Dynamic invoker for setter */
  88     private static Object INVOKE_UA_SETTER = new Object();
  89     private static MethodHandle getINVOKE_UA_SETTER() {
  90         return Context.getGlobal().getDynamicInvoker(INVOKE_UA_SETTER,
  91                 new Callable<MethodHandle>() {
  92                     @Override
  93                     public MethodHandle call() {
  94                         return Bootstrap.createDynamicInvoker("dyn:call", void.class,
  95                             Object.class, Object.class, Object.class);
  96                     }
  97                 });
  98     }
  99 
 100     /**
 101      * Constructor
 102      *
 103      * @param key        property key
 104      * @param flags      property flags
 105      * @param getterSlot getter slot, starting at first embed
 106      * @param setterSlot setter slot, starting at first embed
 107      */
 108     UserAccessorProperty(final String key, final int flags, final int getterSlot, final int setterSlot) {
 109         super(key, flags, -1);
 110         this.getterSlot = getterSlot;
 111         this.setterSlot = setterSlot;
 112     }
 113 
 114     private UserAccessorProperty(final UserAccessorProperty property) {
 115         super(property);
 116         this.getterSlot = property.getterSlot;
 117         this.setterSlot = property.setterSlot;
 118     }
 119 
 120     /**
 121      * Return getter spill slot for this UserAccessorProperty.
 122      * @return getter slot
 123      */
 124     public int getGetterSlot() {
 125         return getterSlot;
 126     }
 127 
 128     /**
 129      * Return setter spill slot for this UserAccessorProperty.
 130      * @return setter slot
 131      */
 132     public int getSetterSlot() {
 133         return setterSlot;
 134     }
 135 
 136     @Override
 137     protected Property copy() {
 138         return new UserAccessorProperty(this);
 139     }
 140 
 141     @Override
 142     public boolean equals(final Object other) {
 143         if (!super.equals(other)) {
 144             return false;
 145         }
 146 
 147         final UserAccessorProperty uc = (UserAccessorProperty) other;
 148         return getterSlot == uc.getterSlot && setterSlot == uc.setterSlot;
 149     }
 150 
 151     @Override
 152     public int hashCode() {
 153         return super.hashCode() ^ getterSlot ^ setterSlot;
 154     }
 155 
 156     /*
 157      * Accessors.
 158      */
 159     @Override
 160     public int getSpillCount() {
 161         return 2;
 162     }
 163 
 164     @Override
 165     public boolean hasGetterFunction(final ScriptObject obj) {
 166         return obj.getSpill(getterSlot) != null;
 167     }
 168 
 169     @Override
 170     public boolean hasSetterFunction(final ScriptObject obj) {
 171         return obj.getSpill(setterSlot) != null;
 172     }
 173 
 174     @Override
 175     public Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
 176         return userAccessorGetter(owner, getGetterSlot(), self);
 177     }
 178 
 179     @Override
 180     public void setObjectValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) {
 181         userAccessorSetter(owner, getSetterSlot(), strict ? getKey() : null, self, value);
 182     }
 183 
 184     @Override
 185     public MethodHandle getGetter(final Class<?> type) {
 186         return Lookup.filterReturnType(USER_ACCESSOR_GETTER.methodHandle(), type);
 187     }
 188 
 189     @Override
 190     public ScriptFunction getGetterFunction(final ScriptObject obj) {
 191         final Object value = obj.getSpill(getterSlot);
 192         return (value instanceof ScriptFunction) ? (ScriptFunction) value : null;
 193     }
 194 
 195     @Override
 196     public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) {
 197         return USER_ACCESSOR_SETTER.methodHandle();
 198     }
 199 
 200     @Override
 201     public ScriptFunction getSetterFunction(final ScriptObject obj) {
 202         final Object value = obj.getSpill(setterSlot);
 203         return (value instanceof ScriptFunction) ? (ScriptFunction) value : null;
 204     }
 205 
 206     // User defined getter and setter are always called by "dyn:call". Note that the user
 207     // getter/setter may be inherited. If so, proto is bound during lookup. In either
 208     // inherited or self case, slot is also bound during lookup. Actual ScriptFunction
 209     // to be called is retrieved everytime and applied.
 210     static Object userAccessorGetter(final ScriptObject proto, final int slot, final Object self) {
 211         final ScriptObject container = (proto != null) ? proto : (ScriptObject)self;
 212         final Object       func      = container.getSpill(slot);
 213 
 214         if (func instanceof ScriptFunction) {
 215             try {
 216                 return getINVOKE_UA_GETTER().invokeExact(func, self);
 217             } catch(final Error|RuntimeException t) {
 218                 throw t;
 219             } catch(final Throwable t) {
 220                 throw new RuntimeException(t);
 221             }
 222         }
 223 
 224         return UNDEFINED;
 225     }
 226 
 227     static void userAccessorSetter(final ScriptObject proto, final int slot, final String name, final Object self, final Object value) {
 228         final ScriptObject container = (proto != null) ? proto : (ScriptObject)self;
 229         final Object       func      = container.getSpill(slot);
 230 
 231         if (func instanceof ScriptFunction) {
 232             try {
 233                 getINVOKE_UA_SETTER().invokeExact(func, self, value);
 234             } catch(final Error|RuntimeException t) {
 235                 throw t;
 236             } catch(final Throwable t) {
 237                 throw new RuntimeException(t);
 238             }
 239         }  else if (name != null) {
 240             throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self));
 241         }
 242     }
 243 
 244 }