package org.dynjs.runtime; import org.dynjs.exception.ThrowException; public class PropertyDescriptor { private static final Object defaultValue = Types.UNDEFINED; private static final Object defaultSet = Types.UNDEFINED; private static final Object defaultGet = Types.UNDEFINED; private static final boolean defaultWritable = false; private static final boolean defaultConfigurable = false; private static final boolean defaultEnumerable = false; private Object value; private Object set; private Object get; // FIXME: 4 full words allocs for these flags. 2 bits per flag can use a single word // (Java uses full word even when data type is smaller) private byte writable; private byte configurable; private byte enumerable; private byte initialized; // Values for the byte flags above private static final byte UNDEFINED_FLAG = -1; private static final byte FALSE_FLAG = 0; private static final byte TRUE_FLAG = 1; public static PropertyDescriptor newAccessorPropertyDescriptor(Object set, Object get) { return newAccessorPropertyDescriptor(set, get, false, false); } public static PropertyDescriptor newAccessorPropertyDescriptor(Object set, Object get, boolean configurable, boolean enumerable) { return new PropertyDescriptor(set, get, configurable, enumerable); } public static PropertyDescriptor newAccessorPropertyDescriptor() { return newAccessorPropertyDescriptor(defaultSet, defaultGet, defaultConfigurable, defaultEnumerable); } public static PropertyDescriptor newDataPropertyDescriptor(Object value, boolean writable, boolean configurable, boolean enumerable) { return new PropertyDescriptor(value, writable, configurable, enumerable); } public static PropertyDescriptor newDataPropertyDescriptor() { return new PropertyDescriptor(defaultValue, defaultWritable, defaultConfigurable, defaultEnumerable); } public static PropertyDescriptor newPropertyDescriptorForObjectInitializer(Object value) { return newPropertyDescriptorForObjectInitializer(null, value); } public static PropertyDescriptor newPropertyDescriptorForObjectInitializer(String name, Object value) { if (name != null && (value instanceof JSFunction)) { ((JSFunction) value).setDebugContext("Object." + name); } return new PropertyDescriptor(value, true, true, true); } public static PropertyDescriptor newPropertyDescriptorForObjectInitializerGet(Object orig, String name, JSFunction value) { value.setDebugContext("Object.get " + name); PropertyDescriptor d = null; if (orig == Types.UNDEFINED) { d = new PropertyDescriptor(); } else { d = (PropertyDescriptor) orig; } d.get = value; d.setConfigurable(true); d.setEnumerable(true); return d; } public static PropertyDescriptor newPropertyDescriptorForObjectInitializerSet(Object orig, String name, JSFunction value) { value.setDebugContext("Object.set " + name); PropertyDescriptor d = null; if (orig == Types.UNDEFINED) { d = new PropertyDescriptor(); } else { d = (PropertyDescriptor) orig; } d.set = value; d.setConfigurable(true); d.setEnumerable(true); return d; } private PropertyDescriptor(Object set, Object get, boolean configurable, boolean enumerable) { this.set = set; this.get = get; this.writable = UNDEFINED_FLAG; this.configurable = configurable ? TRUE_FLAG : FALSE_FLAG; this.enumerable = enumerable ? TRUE_FLAG : FALSE_FLAG; this.initialized = UNDEFINED_FLAG; } private PropertyDescriptor(Object value, boolean writable, boolean configurable, boolean enumerable) { this.value = value; this.writable = writable ? TRUE_FLAG : FALSE_FLAG; this.configurable = configurable ? TRUE_FLAG : FALSE_FLAG; this.enumerable = enumerable ? TRUE_FLAG : FALSE_FLAG; this.initialized = UNDEFINED_FLAG; } public PropertyDescriptor() { this.writable = UNDEFINED_FLAG; this.configurable = UNDEFINED_FLAG; this.enumerable = UNDEFINED_FLAG; this.initialized = UNDEFINED_FLAG; } public String toString() { return "[PropertyDescriptor value=" + this.value + "; writable=" + this.isWritable() + "; enumerable=" + this.isEnumerable() + "; configurable=" + this.isConfigurable() + "; setter=" + this.set+ "; getter=" + this.get + "]"; } public boolean isWritable() { return this.writable == TRUE_FLAG ? true : false; } public Object getWritable() { return !hasWritable() ? Types.UNDEFINED : isWritable(); } public boolean hasWritable() { return this.writable != UNDEFINED_FLAG; } public void setWritable(boolean writable) { this.writable = writable ? TRUE_FLAG : FALSE_FLAG; } public boolean isConfigurable() { return this.configurable == TRUE_FLAG ? true : false; } public Object getConfigurable() { return !hasConfigurable() ? Types.UNDEFINED : isConfigurable(); } public boolean hasConfigurable() { return this.configurable != UNDEFINED_FLAG; } public void setConfigurable(boolean configurable) { this.configurable = configurable ? TRUE_FLAG : FALSE_FLAG; } public boolean isEnumerable() { return this.enumerable == TRUE_FLAG ? true : false; } public Object getEnumerable() { return !hasEnumerable() ? Types.UNDEFINED : isEnumerable(); } public void setEnumerable(boolean enumerable) { this.enumerable = enumerable ? TRUE_FLAG : FALSE_FLAG; } public boolean hasEnumerable() { return this.enumerable != UNDEFINED_FLAG; } public boolean hasInitialized() { return this.initialized != UNDEFINED_FLAG; } public void setInitialized(boolean value) { this.initialized = value ? TRUE_FLAG : FALSE_FLAG; } public Object getValue() { return this.value; } public void setValue(Object value) { this.value = value; } public boolean hasValue() { return this.value != null; } public Object getSetter() { return this.set; } public void setSetter(Object setter) { this.set = setter; } public boolean hasSet() { return this.set != null; } public Object getGetter() { return this.get; } public void setGetter(Object getter) { this.get = getter; } public boolean hasGet() { return this.get != null; } public boolean isEmpty() { return this.value == null && this.set == null && this.get == null && !hasWritable() && !hasConfigurable() && !hasEnumerable(); } public PropertyDescriptor duplicate() { // 8.12.1 (steps 2-7) PropertyDescriptor d = new PropertyDescriptor(); if (isDataDescriptor()) { // System.err.println("isData"); d.value = this.value; d.writable = this.writable; } else { // System.err.println("isAccesor"); d.get = this.get; d.set = this.set; } d.enumerable = this.enumerable; d.configurable = this.configurable; return d; } public PropertyDescriptor duplicateWithDefaults() { // 8.12.9 (steps 4 a+b) PropertyDescriptor d = new PropertyDescriptor(); if (isGenericDescriptor() || isDataDescriptor()) { d.value = this.value != null ? this.value : Types.UNDEFINED; d.writable = this.writable == UNDEFINED_FLAG ? FALSE_FLAG : this.writable; } else { d.get = this.get != null ? this.get : Types.UNDEFINED; d.set = this.set != null ? this.set : Types.UNDEFINED; } d.enumerable = this.enumerable == UNDEFINED_FLAG ? FALSE_FLAG : this.enumerable; d.configurable = this.configurable == UNDEFINED_FLAG ? FALSE_FLAG : this.configurable; return d; } public void copyAll(PropertyDescriptor from) { if (from.value != null) { this.value = from.value; } if (from.set != null) { this.set = from.set; } if (from.get != null) { this.get = from.get; } if (from.hasWritable()) { setWritable(from.isWritable()); } if (from.hasConfigurable()) { setConfigurable(from.isConfigurable()); } if (from.hasEnumerable()) { setEnumerable(from.isEnumerable()); } } public boolean isAccessorDescriptor() { // 8.10.1 return this.get != null || this.set != null; } public boolean isDataDescriptor() { // 8.10.2 return this.value != null || hasWritable(); } public boolean isGenericDescriptor() { // 8.10.3 return isAccessorDescriptor() == false && isDataDescriptor() == false; } public static Object fromPropertyDescriptor(ExecutionContext context, Object d) { // 8.10.4 if (d == Types.UNDEFINED) { return Types.UNDEFINED; } final PropertyDescriptor desc = (PropertyDescriptor) d; JSObject obj = new DynObject(context.getGlobalContext()); if (desc.isDataDescriptor()) { obj.defineOwnProperty(context, "value", PropertyDescriptor.newDataPropertyDescriptor(desc.getValue(), true, true, true), false); obj.defineOwnProperty(context, "writable", PropertyDescriptor.newDataPropertyDescriptor(desc.getWritable(), true, true, true), false); } else { obj.defineOwnProperty(context, "get", PropertyDescriptor.newDataPropertyDescriptor(desc.getGetter(), true, true, true), false); obj.defineOwnProperty(context, "set", PropertyDescriptor.newDataPropertyDescriptor(desc.getSetter(), true, true, true), false); } obj.defineOwnProperty(context, "enumerable", PropertyDescriptor.newDataPropertyDescriptor(desc.getEnumerable(), true, true, true), false); obj.defineOwnProperty(context, "configurable", PropertyDescriptor.newDataPropertyDescriptor(desc.getConfigurable(), true, true, true), false); return obj; } public static PropertyDescriptor toPropertyDescriptor(ExecutionContext context, Object o) { // 8.10.5 if (!(o instanceof JSObject)) { throw new ThrowException(context, context.createTypeError("attributes must be an object")); } JSObject obj = (JSObject) o; PropertyDescriptor d = new PropertyDescriptor(); if (obj.hasProperty(context, "enumerable")) { d.setEnumerable(Types.toBoolean(obj.get(context, "enumerable"))); } if (obj.hasProperty(context, "configurable")) { d.setConfigurable(Types.toBoolean(obj.get(context, "configurable"))); } if (obj.hasProperty(context, "value")) { d.setValue(obj.get(context, "value")); } if (obj.hasProperty(context, "writable")) { d.setWritable(Types.toBoolean(obj.get(context, "writable"))); } if (obj.hasProperty(context, "get")) { Object getter = obj.get(context, "get"); if (!Types.isCallable(getter) && getter != Types.UNDEFINED) { throw new ThrowException(context, context.createTypeError("get must be callable")); } d.get = getter; } if (obj.hasProperty(context, "set")) { Object setter = obj.get(context, "set"); if ((!Types.isCallable(setter)) && setter != Types.UNDEFINED) { throw new ThrowException(context, context.createTypeError("set must be callable")); } d.set = setter; } if ((d.hasGet() || d.hasSet()) && (d.hasWritable() || d.hasValue())) { throw new ThrowException(context, context.createTypeError("may not be both a data property and an accessor property")); } return d; } }