/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.types;
import static com.github.anba.es6draft.runtime.AbstractOperations.*;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import static com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject.ObjectCreate;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject;
/**
* <h1>6 ECMAScript Data Types and Values</h1><br>
* <h2>6.2 ECMAScript Specification Types</h2>
* <ul>
* <li>6.2.4 The Property Descriptor Specification Type
* </ul>
*/
public final class PropertyDescriptor implements Cloneable {
private static final int VALUE = 0x01;
private static final int GET = 0x02;
private static final int SET = 0x04;
private static final int WRITABLE = 0x08;
private static final int ENUMERABLE = 0x10;
private static final int CONFIGURABLE = 0x20;
private static final int POPULATED_ACCESSOR_DESC = GET | SET | ENUMERABLE | CONFIGURABLE;
private static final int POPULATED_DATA_DESC = VALUE | WRITABLE | ENUMERABLE | CONFIGURABLE;
private int present = 0;
// default attribute values per 6.1.7.1, table 3
private Object value = UNDEFINED;
private Callable getter = null; // = Undefined
private Callable setter = null; // = Undefined
private boolean writable = false;
private boolean enumerable = false;
private boolean configurable = false;
/**
* Constructs a new empty property descriptor record.
*/
public PropertyDescriptor() {
}
/**
* Constructs a shallow copy of the supplied property descriptor.
*
* @param original
* the original property descriptor
*/
private PropertyDescriptor(PropertyDescriptor original) {
present = original.present;
value = original.value;
getter = original.getter;
setter = original.setter;
writable = original.writable;
enumerable = original.enumerable;
configurable = original.configurable;
}
/**
* Constructs a shallow copy of the supplied property.
*
* @param original
* the original property
*/
/* package */ PropertyDescriptor(Property original) {
present = original.isDataDescriptor() ? POPULATED_DATA_DESC : POPULATED_ACCESSOR_DESC;
value = original.getValue();
getter = original.getGetter();
setter = original.getSetter();
writable = original.isWritable();
enumerable = original.isEnumerable();
configurable = original.isConfigurable();
}
/**
* Constructs a new data property descriptor with an initial value:<br>
* <code>{[[Value]]: ?}</code>
*
* @param value
* the property value
*/
public PropertyDescriptor(Object value) {
this.value = value;
this.present = VALUE;
}
/**
* Constructs a new data property descriptor with an initial value and initial attributes:<br>
* <code>{[[Value]]: ?, [[Writable]]: ?, [[Enumerable]]: ?, [[Configurable]]: ?}</code>
*
* @param value
* the property value
* @param writable
* the enumerable flag
* @param enumerable
* the writable flag
* @param configurable
* the configurable flag
*/
public PropertyDescriptor(Object value, boolean writable, boolean enumerable, boolean configurable) {
this.value = value;
this.writable = writable;
this.enumerable = enumerable;
this.configurable = configurable;
this.present = VALUE | WRITABLE | ENUMERABLE | CONFIGURABLE;
}
/**
* Constructs a new accessor property descriptor with initial getter and setter and initial attributes:<br>
* <code>{[[Get]]: ?, [[Set]]: ?, [[Enumerable]]: ?, [[Configurable]]: ?}</code>
*
* @param getter
* the accessor getter function, may be {@code null}
* @param setter
* the accessor setter function, may be {@code null}
* @param enumerable
* the writable flag
* @param configurable
* the configurable flag
*/
public PropertyDescriptor(Callable getter, Callable setter, boolean enumerable, boolean configurable) {
this.getter = getter;
this.setter = setter;
this.enumerable = enumerable;
this.configurable = configurable;
this.present = GET | SET | ENUMERABLE | CONFIGURABLE;
}
/**
* Constructs a new accessor property descriptor with initial getter and setter and initial attributes:<br>
* <code>{[[Get]]: ?, [[Set]]: ?, [[Enumerable]]: ?, [[Configurable]]: ?}</code>
*
* @param getter
* the accessor getter function, may be {@code null}
* @param setter
* the accessor setter function, may be {@code null}
* @param enumerable
* the writable flag
* @param configurable
* the configurable flag
* @return the new accessor property descriptor
*/
public static PropertyDescriptor AccessorPropertyDescriptor(Callable getter, Callable setter, boolean enumerable,
boolean configurable) {
PropertyDescriptor desc = new PropertyDescriptor(getter, setter, enumerable, configurable);
if (getter == null) {
desc.present &= ~GET;
}
if (setter == null) {
desc.present &= ~SET;
}
return desc;
}
/**
* Converts this property descriptor to a {@link Property} object.
*
* @return the property record for this descriptor
*/
public Property toProperty() {
return new Property(this);
}
@Override
public PropertyDescriptor clone() {
return new PropertyDescriptor(this);
}
/**
* 6.2.4.1 IsAccessorDescriptor ( Desc )
*
* @param desc
* the property descriptor
* @return {@code true} if the descriptor is an accessor property
*/
public static boolean IsAccessorDescriptor(PropertyDescriptor desc) {
/* step 1 */
if (desc == null) {
return false;
}
/* steps 2-3 */
return desc.isAccessorDescriptor();
}
/**
* 6.2.4.1 IsAccessorDescriptor ( Desc )<br>
* Returns {@code true} if this object is an accessor property descriptor.
*
* @return {@code true} if this descriptor is an accessor property
*/
public boolean isAccessorDescriptor() {
return (present & (GET | SET)) != 0;
}
/**
* 6.2.4.2 IsDataDescriptor ( Desc )
*
* @param desc
* the property descriptor
* @return {@code true} if the descriptor is a data property
*/
public static boolean IsDataDescriptor(PropertyDescriptor desc) {
/* step 1 */
if (desc == null) {
return false;
}
/* steps 2-3 */
return desc.isDataDescriptor();
}
/**
* 6.2.4.2 IsDataDescriptor ( Desc )<br>
* Returns {@code true} if this object is a data property descriptor.
*
* @return {@code true} if this descriptor is a data property
*/
public boolean isDataDescriptor() {
return (present & (VALUE | WRITABLE)) != 0;
}
/**
* 6.2.4.3 IsGenericDescriptor ( Desc )
*
* @param desc
* the property descriptor
* @return {@code true} if the descriptor is a generic property
*/
public static boolean IsGenericDescriptor(PropertyDescriptor desc) {
/* step 1 */
if (desc == null) {
return false;
}
/* steps 2-3 */
return desc.isGenericDescriptor();
}
/**
* 6.2.4.3 IsGenericDescriptor ( Desc )<br>
* Returns {@code true} if this object is a generic property descriptor.
*
* @return {@code true} if this descriptor is a generic property
*/
public boolean isGenericDescriptor() {
return (present & (GET | SET | VALUE | WRITABLE)) == 0;
}
/**
* 6.2.4.4 FromPropertyDescriptor ( Desc )
* <p>
* Returns {@code undefined} if the input property descriptor is {@code null}, otherwise returns a
* {@link ScriptObject} representing the fields of this property descriptor.
*
* @param cx
* the execution context
* @param desc
* the property record
* @return a script object for the property, or undefined if <var>desc</var> is {@code null}
*/
public static Object FromPropertyDescriptor(ExecutionContext cx, Property desc) throws IllegalArgumentException {
/* step 1 */
if (desc == null) {
return UNDEFINED;
}
/* steps 2-3 */
OrdinaryObject obj = ObjectCreate(cx, Intrinsics.ObjectPrototype);
/* steps 4-9 */
if (desc.isDataDescriptor()) {
obj.defineOwnProperty(cx, "value", _p(desc.getValue()));
obj.defineOwnProperty(cx, "writable", _p(desc.isWritable()));
} else {
obj.defineOwnProperty(cx, "get", _p(undefinedIfNull(desc.getGetter())));
obj.defineOwnProperty(cx, "set", _p(undefinedIfNull(desc.getSetter())));
}
obj.defineOwnProperty(cx, "enumerable", _p(desc.isEnumerable()));
obj.defineOwnProperty(cx, "configurable", _p(desc.isConfigurable()));
/* step 10 */
return obj;
}
/**
* 6.2.4.4 FromPropertyDescriptor ( Desc )
* <p>
* Returns {@code undefined} if the input property descriptor is {@code null}, otherwise returns a
* {@link ScriptObject} representing the fields of this property descriptor.
*
* @param cx
* the execution context
* @param desc
* the property descriptor
* @return a script object for the property, or undefined if <var>desc</var> is {@code null}
*/
public static Object FromPropertyDescriptor(ExecutionContext cx, PropertyDescriptor desc)
throws IllegalArgumentException {
assert desc == null || !(desc.isDataDescriptor() && desc.isAccessorDescriptor());
/* step 1 */
if (desc == null) {
return UNDEFINED;
}
/* steps 2-3 */
OrdinaryObject obj = ObjectCreate(cx, Intrinsics.ObjectPrototype);
/* steps 4-9 */
if (desc.hasValue()) {
obj.defineOwnProperty(cx, "value", _p(desc.getValue()));
}
if (desc.hasWritable()) {
obj.defineOwnProperty(cx, "writable", _p(desc.isWritable()));
}
if (desc.hasGetter()) {
obj.defineOwnProperty(cx, "get", _p(undefinedIfNull(desc.getGetter())));
}
if (desc.hasSetter()) {
obj.defineOwnProperty(cx, "set", _p(undefinedIfNull(desc.getSetter())));
}
if (desc.hasEnumerable()) {
obj.defineOwnProperty(cx, "enumerable", _p(desc.isEnumerable()));
}
if (desc.hasConfigurable()) {
obj.defineOwnProperty(cx, "configurable", _p(desc.isConfigurable()));
}
/* step 10 */
return obj;
}
private static PropertyDescriptor _p(Object value) {
return new PropertyDescriptor(value, true, true, true);
}
private static Object undefinedIfNull(Callable value) {
return value != null ? value : UNDEFINED;
}
/**
* 6.2.4.5 ToPropertyDescriptor ( Obj )
* <p>
* Returns a new property descriptor from the input argument {@code object}, if {@code object} is not an instance of
* {@link ScriptObject}, a TypeError is thrown.
*
* @param cx
* the execution context
* @param object
* the property descriptor script object
* @return the property descriptor record
*/
public static PropertyDescriptor ToPropertyDescriptor(ExecutionContext cx, Object object) {
/* steps 1-2 */
if (!Type.isObject(object)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
ScriptObject obj = Type.objectValue(object);
/* steps 3-9 */
PropertyDescriptor desc = new PropertyDescriptor();
if (HasProperty(cx, obj, "enumerable")) {
boolean enumerable = ToBoolean(Get(cx, obj, "enumerable"));
desc.setEnumerable(enumerable);
}
if (HasProperty(cx, obj, "configurable")) {
boolean configurable = ToBoolean(Get(cx, obj, "configurable"));
desc.setConfigurable(configurable);
}
if (HasProperty(cx, obj, "value")) {
Object value = Get(cx, obj, "value");
desc.setValue(value);
}
if (HasProperty(cx, obj, "writable")) {
boolean writable = ToBoolean(Get(cx, obj, "writable"));
desc.setWritable(writable);
}
if (HasProperty(cx, obj, "get")) {
Object getter = Get(cx, obj, "get");
if (!(IsCallable(getter) || Type.isUndefined(getter))) {
throw newTypeError(cx, Messages.Key.InvalidGetter);
}
desc.setGetter(callableOrNull(getter));
}
if (HasProperty(cx, obj, "set")) {
Object setter = Get(cx, obj, "set");
if (!(IsCallable(setter) || Type.isUndefined(setter))) {
throw newTypeError(cx, Messages.Key.InvalidSetter);
}
desc.setSetter(callableOrNull(setter));
}
/* step 10 */
if ((desc.present & (GET | SET)) != 0 && (desc.present & (VALUE | WRITABLE)) != 0) {
throw newTypeError(cx, Messages.Key.InvalidDescriptor);
}
/* step 11 */
return desc;
}
private static Callable callableOrNull(Object value) {
return value instanceof Callable ? (Callable) value : null;
}
/**
* 6.2.4.6 CompletePropertyDescriptor ( Desc, LikeDesc )
*
* @param desc
* the property descriptor
* @return the <var>desc</var> parameter
*/
public static PropertyDescriptor CompletePropertyDescriptor(PropertyDescriptor desc) {
/* steps 1-2 (implicit) */
/* step 3 (omitted) */
/* steps 4-5 */
if (IsGenericDescriptor(desc) || IsDataDescriptor(desc)) {
/* step 4 */
if (!desc.hasValue()) {
desc.setValue(UNDEFINED);
}
if (!desc.hasWritable()) {
desc.setWritable(false);
}
} else {
/* step 5 */
if (!desc.hasGetter()) {
desc.setGetter(null);
}
if (!desc.hasSetter()) {
desc.setSetter(null);
}
}
/* step 6 */
if (!desc.hasEnumerable()) {
desc.setEnumerable(false);
}
/* step 7 */
if (!desc.hasConfigurable()) {
desc.setConfigurable(false);
}
/* step 8 */
return desc;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append('{');
if (hasWritable()) {
sb.append("[[Writable]]: ").append(isWritable()).append(", ");
}
if (hasEnumerable()) {
sb.append("[[Enumerable]]: ").append(isEnumerable()).append(", ");
}
if (hasConfigurable()) {
sb.append("[[Configurable]]: ").append(isConfigurable()).append(", ");
}
if (hasValue()) {
sb.append("[[Value]]: ").append(getValue()).append(", ");
}
if (hasGetter()) {
sb.append("[[Get]]: ").append(getGetter()).append(", ");
}
if (hasSetter()) {
sb.append("[[Set]]: ").append(getSetter()).append(", ");
}
if (sb.length() > 1) {
sb.setLength(sb.length() - 2);
}
sb.append('}');
return sb.toString();
}
/**
* Returns {@code true} if every field of this property descriptor is absent.
*
* @return {@code true} if every field is absent
*/
public boolean isEmpty() {
return present == 0;
}
/**
* Returns {@code true} if every field of {@code desc} also occurs in this property descriptor and every present
* field has the same value. That means {@code true} is returned iff {@code desc} ⊆ {@code this} holds.
*
* @param desc
* the property descriptor
* @return {@code true} if <var>desc</var> if a subset of this property descriptor
*/
public boolean isSubset(PropertyDescriptor desc) {
if (desc.hasValue() && !(hasValue() && SameValue(desc.value, value))) {
return false;
}
if (desc.hasGetter() && !(hasGetter() && desc.getter == getter)) {
return false;
}
if (desc.hasSetter() && !(hasSetter() && desc.setter == setter)) {
return false;
}
if (desc.hasWritable() && !(hasWritable() && desc.writable == writable)) {
return false;
}
if (desc.hasEnumerable() && !(hasEnumerable() && desc.enumerable == enumerable)) {
return false;
}
if (desc.hasConfigurable() && !(hasConfigurable() && desc.configurable == configurable)) {
return false;
}
return true;
}
/**
* Returns {@code true} if the <code>[[Value]]</code> field is present.
*
* @return {@code true} if the value field is present
*/
public boolean hasValue() {
return (present & VALUE) != 0;
}
/**
* Returns {@code true} if the <code>[[Get]]</code> field is present.
*
* @return {@code true} if the getter field is present
*/
public boolean hasGetter() {
return (present & GET) != 0;
}
/**
* Returns {@code true} if the <code>[[Set]]</code> field is present.
*
* @return {@code true} if the setter field is present
*/
public boolean hasSetter() {
return (present & SET) != 0;
}
/**
* Returns {@code true} if the <code>[[Writable]]</code> field is present.
*
* @return {@code true} if the writable field is present
*/
public boolean hasWritable() {
return (present & WRITABLE) != 0;
}
/**
* Returns {@code true} if the <code>[[Enumerable]]</code> field is present.
*
* @return {@code true} if the enumerable field is present
*/
public boolean hasEnumerable() {
return (present & ENUMERABLE) != 0;
}
/**
* Returns {@code true} if the <code>[[Configurable]]</code> field is present.
*
* @return {@code true} if the configurable field is present
*/
public boolean hasConfigurable() {
return (present & CONFIGURABLE) != 0;
}
/**
* Returns the <code>[[Value]]</code> field or its default value.
*
* @return the value field
*/
public Object getValue() {
return value;
}
/**
* Sets the <code>[[Value]]</code> field to the argument value.
*
* @param value
* the new value
*/
public void setValue(Object value) {
present |= VALUE;
this.value = value;
}
/**
* Returns the <code>[[Get]]</code> field or its default value.
*
* @return the getter accessor field
*/
public Callable getGetter() {
return getter;
}
/**
* Sets the <code>[[Get]]</code> field to the argument value.
*
* @param getter
* the new getter function
*/
public void setGetter(Callable getter) {
present |= GET;
this.getter = getter;
}
/**
* Returns the <code>[[Set]]</code> field or its default value.
*
* @return the setter accessor field
*/
public Callable getSetter() {
return setter;
}
/**
* Sets the <code>[[Set]]</code> field to the argument value.
*
* @param setter
* the new setter function
*/
public void setSetter(Callable setter) {
present |= SET;
this.setter = setter;
}
/**
* Returns the <code>[[Writable]]</code> field or its default value.
*
* @return the writable field
*/
public boolean isWritable() {
return writable;
}
/**
* Sets the <code>[[Writable]]</code> field to the argument value.
*
* @param writable
* the new writable mode
*/
public void setWritable(boolean writable) {
present |= WRITABLE;
this.writable = writable;
}
/**
* Returns the <code>[[Enumerable]]</code> field or its default value.
*
* @return the enumerable field
*/
public boolean isEnumerable() {
return enumerable;
}
/**
* Sets the <code>[[Enumerable]]</code> field to the argument value.
*
* @param enumerable
* the new enumerable mode
*/
public void setEnumerable(boolean enumerable) {
present |= ENUMERABLE;
this.enumerable = enumerable;
}
/**
* Returns the <code>[[Configurable]]</code> field or its default value.
*
* @return the configurable field
*/
public boolean isConfigurable() {
return configurable;
}
/**
* Sets the <code>[[Configurable]]</code> field to the argument value.
*
* @param configurable
* the new configurable mode
*/
public void setConfigurable(boolean configurable) {
present |= CONFIGURABLE;
this.configurable = configurable;
}
}