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 static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  38 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
  39 
  40 /**
  41  * Property with user defined getters/setters. Actual getter and setter
  42  * functions are stored in underlying ScriptObject. Only the 'slot' info is
  43  * stored in the property.
  44  *
  45  * The slots here denote either ScriptObject embed field number or spill
  46  * array index. For spill array index, we use slot value of
  47  * (index + ScriptObject.embedSize). See also ScriptObject.getEmbedOrSpill
  48  * method. Negative slot value means that the corresponding getter or setter
  49  * is null. Note that always two slots are allocated in ScriptObject - but
  50  * negative (less by 1) slot number is stored for null getter or setter.
  51  * This is done so that when the property is redefined with a different
  52  * getter and setter (say, both non-null), we'll have spill slots to store
  53  * those. When a slot is negative, (-slot - 1) is the embed/spill index.
  54  */
  55 public final class UserAccessorProperty extends Property {
  56 
  57     /** User defined getter function slot. */
  58     private final int getterSlot;
  59 
  60     /** User defined setter function slot. */
  61     private final int setterSlot;
  62 
  63     /** Getter method handle */
  64     private final static CompilerConstants.Call USER_ACCESSOR_GETTER = staticCall(MethodHandles.lookup(), UserAccessorProperty.class,
  65             "userAccessorGetter", Object.class, ScriptObject.class, int.class, Object.class);
  66 
  67     /** Setter method handle */
  68     private final static CompilerConstants.Call USER_ACCESSOR_SETTER = staticCall(MethodHandles.lookup(), UserAccessorProperty.class,
  69             "userAccessorSetter", void.class, ScriptObject.class, int.class, String.class, Object.class, Object.class);
  70 
  71     /** Dynamic invoker for getter */
  72     private static final Object INVOKE_UA_GETTER = new Object();
  73 
  74     private static MethodHandle getINVOKE_UA_GETTER() {
  75 
  76         return ((GlobalObject)Context.getGlobal()).getDynamicInvoker(INVOKE_UA_GETTER,
  77                 new Callable<MethodHandle>() {
  78                     @Override
  79                     public MethodHandle call() {
  80                         return Bootstrap.createDynamicInvoker("dyn:call", Object.class,
  81                             Object.class, Object.class);
  82                     }
  83                 });
  84     }
  85 
  86     /** Dynamic invoker for setter */
  87     private static Object INVOKE_UA_SETTER = new Object();
  88     private static MethodHandle getINVOKE_UA_SETTER() {
  89         return ((GlobalObject)Context.getGlobal()).getDynamicInvoker(INVOKE_UA_SETTER,
  90                 new Callable<MethodHandle>() {
  91                     @Override
  92                     public MethodHandle call() {
  93                         return Bootstrap.createDynamicInvoker("dyn:call", void.class,
  94                             Object.class, Object.class, Object.class);
  95                     }
  96                 });
  97     }
  98 
  99     /**
 100      * Constructor
 101      *
 102      * @param key        property key
 103      * @param flags      property flags
 104      * @param getterSlot getter slot, starting at first embed
 105      * @param setterSlot setter slot, starting at first embed
 106      */
 107     UserAccessorProperty(final String key, final int flags, final int getterSlot, final int setterSlot) {
 108         super(key, flags, -1);
 109         this.getterSlot = getterSlot;
 110         this.setterSlot = setterSlot;
 111     }
 112 
 113     private UserAccessorProperty(final UserAccessorProperty property) {
 114         super(property);
 115         this.getterSlot = property.getterSlot;
 116         this.setterSlot = property.setterSlot;
 117     }
 118 
 119     /**
 120      * Return getter spill slot for this UserAccessorProperty.
 121      * @return getter slot
 122      */
 123     public int getGetterSlot() {
 124         return getterSlot;
 125     }
 126 
 127     /**
 128      * Return setter spill slot for this UserAccessorProperty.
 129      * @return setter slot
 130      */
 131     public int getSetterSlot() {
 132         return setterSlot;
 133     }
 134 
 135     @Override
 136     protected Property copy() {
 137         return new UserAccessorProperty(this);
 138     }
 139 
 140     @Override
 141     public boolean equals(final Object other) {
 142         if (!super.equals(other)) {
 143             return false;
 144         }
 145 
 146         final UserAccessorProperty uc = (UserAccessorProperty) other;
 147         return getterSlot == uc.getterSlot && setterSlot == uc.setterSlot;
 148     }
 149 
 150     @Override
 151     public int hashCode() {
 152         return super.hashCode() ^ getterSlot ^ setterSlot;
 153     }
 154 
 155     /*
 156      * Accessors.
 157      */
 158     @Override
 159     public int getSpillCount() {
 160         return 2;
 161     }
 162 
 163     @Override
 164     public boolean hasGetterFunction(final ScriptObject obj) {
 165         return obj.getSpill(getterSlot) != null;
 166     }
 167 
 168     @Override
 169     public boolean hasSetterFunction(final ScriptObject obj) {
 170         return obj.getSpill(setterSlot) != null;
 171     }
 172 
 173     @Override
 174     public Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
 175         return userAccessorGetter(owner, getGetterSlot(), self);
 176     }
 177 
 178     @Override
 179     public void setObjectValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) {
 180         userAccessorSetter(owner, getSetterSlot(), strict ? getKey() : null, self, value);
 181     }
 182 
 183     @Override
 184     public MethodHandle getGetter(final Class<?> type) {
 185         return Lookup.filterReturnType(USER_ACCESSOR_GETTER.methodHandle(), type);
 186     }
 187 
 188     @Override
 189     public ScriptFunction getGetterFunction(final ScriptObject obj) {
 190         final Object value = obj.getSpill(getterSlot);
 191         return (value instanceof ScriptFunction) ? (ScriptFunction) value : null;
 192     }
 193 
 194     @Override
 195     public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) {
 196         return USER_ACCESSOR_SETTER.methodHandle();
 197     }
 198 
 199     @Override
 200     public ScriptFunction getSetterFunction(final ScriptObject obj) {
 201         final Object value = obj.getSpill(setterSlot);
 202         return (value instanceof ScriptFunction) ? (ScriptFunction) value : null;
 203     }
 204 
 205     // User defined getter and setter are always called by "dyn:call". Note that the user
 206     // getter/setter may be inherited. If so, proto is bound during lookup. In either
 207     // inherited or self case, slot is also bound during lookup. Actual ScriptFunction
 208     // to be called is retrieved everytime and applied.
 209     static Object userAccessorGetter(final ScriptObject proto, final int slot, final Object self) {
 210         final ScriptObject container = (proto != null) ? proto : (ScriptObject)self;
 211         final Object       func      = container.getSpill(slot);
 212 
 213         if (func instanceof ScriptFunction) {
 214             try {
 215                 return getINVOKE_UA_GETTER().invokeExact(func, self);
 216             } catch(final Error|RuntimeException t) {
 217                 throw t;
 218             } catch(final Throwable t) {
 219                 throw new RuntimeException(t);
 220             }
 221         }
 222 
 223         return UNDEFINED;
 224     }
 225 
 226     static void userAccessorSetter(final ScriptObject proto, final int slot, final String name, final Object self, final Object value) {
 227         final ScriptObject container = (proto != null) ? proto : (ScriptObject)self;
 228         final Object       func      = container.getSpill(slot);
 229 
 230         if (func instanceof ScriptFunction) {
 231             try {
 232                 getINVOKE_UA_SETTER().invokeExact(func, self, value);
 233             } catch(final Error|RuntimeException t) {
 234                 throw t;
 235             } catch(final Throwable t) {
 236                 throw new RuntimeException(t);
 237             }
 238         }  else if (name != null) {
 239             throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self));
 240         }
 241     }
 242 
 243 }