// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.sdk.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.chromium.sdk.JsFunction;
import org.chromium.sdk.JsObject;
import org.chromium.sdk.JsVariable;
import org.chromium.sdk.internal.tools.v8.MethodIsBlockingException;
/**
* A generic implementation of the JsObject interface.
*/
class JsObjectImpl extends JsValueImpl implements JsObject {
private final InternalContext context;
/**
* Fully qualified name of variable holding this object.
*/
private final String variableFqn;
/**
* This constructor implies the lazy resolution of object properties.
*
* @param context where this instance belongs in
* @param variableFqn the fully qualified name of the variable holding this object
* @param valueState the value data from the JS VM
*/
JsObjectImpl(InternalContext context, String variableFqn, ValueMirror valueState) {
super(valueState);
this.context = context;
this.variableFqn = variableFqn;
}
public Collection<JsVariableImpl> getProperties() throws MethodIsBlockingException {
return subproperties.getPropertiesLazily();
}
public Collection<JsVariableImpl> getInternalProperties() throws MethodIsBlockingException {
return internalProperties.getPropertiesLazily();
}
public String getRefId() {
int ref = getMirror().getRef();
if (ref < 0) {
// Negative handle means that it's transient. We don't expose it.
return null;
} else {
return String.valueOf(ref);
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("[JsObject: type=").append(getType());
try {
for (JsVariable prop : getProperties()) {
result.append(',').append(prop);
}
} catch (MethodIsBlockingException e) {
return "[JsObject: Exception in retrieving data]";
}
result.append(']');
return result.toString();
}
@Override
public JsObjectImpl asObject() {
return this;
}
public JsArrayImpl asArray() {
return null;
}
public JsFunction asFunction() {
return null;
}
public JsVariable getProperty(String name) {
return subproperties.getProperty(name);
}
public String getClassName() {
return getMirror().getClassName();
}
protected InternalContext getInternalContext() {
return context;
}
Subproperties getSubpropertiesHelper() {
return subproperties;
}
protected SubpropertiesMirror getSubpropertiesMirror() {
return context.getValueLoader().loadSubpropertiesInMirror(getMirror()).getSubpropertiesMirror();
}
abstract class Subproperties {
private List<JsVariableImpl> properties = null;
private Map<String, JsVariableImpl> propertyMap = null;
/**
* Calls to this method must be synchronized on propertyLock.
*/
private Map<String, JsVariableImpl> ensurePropertyMap() {
if (propertyMap == null) {
List<JsVariableImpl> propertiesList = getPropertiesLazily();
Map<String, JsVariableImpl> map =
new HashMap<String, JsVariableImpl>(propertiesList.size() * 2, 0.75f);
for (JsVariableImpl prop : propertiesList) {
map.put(prop.getName(), prop);
}
propertyMap = Collections.unmodifiableMap(map);
}
return propertyMap;
}
private List<JsVariableImpl> createPropertiesFromMirror(List<ValueMirror> mirrorProperties,
List<? extends PropertyReference> propertyRefs) throws MethodIsBlockingException {
// TODO(peter.rybin) Maybe assert that context is valid here
List<JsVariableImpl> result = new ArrayList<JsVariableImpl>(mirrorProperties.size());
for (int i = 0; i < mirrorProperties.size(); i++) {
ValueMirror mirror = mirrorProperties.get(i);
Object varName = propertyRefs.get(i).getName();
String fqn = getFullyQualifiedName(varName);
if (fqn == null) {
continue;
}
String decoratedName = JsVariableImpl.NameDecorator.decorateVarName(varName);
result.add(new JsVariableImpl(context, mirror, varName, decoratedName, fqn));
}
return result;
}
private String getFullyQualifiedName(Object propName) {
if (variableFqn == null) {
return null;
}
if (propName instanceof String) {
String propNameStr = (String) propName;
if (propNameStr.startsWith(".")) {
// ".arguments" is not legal
return null;
}
}
return variableFqn + JsVariableImpl.NameDecorator.buildAccessSuffix(propName);
}
JsVariableImpl getProperty(String propertyName) {
return ensurePropertyMap().get(propertyName);
}
List<JsVariableImpl> getPropertiesLazily() throws MethodIsBlockingException {
synchronized (this) {
if (properties == null) {
List<? extends PropertyReference> propertyRefs =
getPropertyRefs(getSubpropertiesMirror());
ValueLoader valueLoader = context.getValueLoader();
List<ValueMirror> subMirrors = valueLoader.getOrLoadValueFromRefs(propertyRefs);
List<JsVariableImpl> wrappedProperties = createPropertiesFromMirror(subMirrors,
propertyRefs);
properties = Collections.unmodifiableList(wrappedProperties);
}
return properties;
}
}
abstract List<? extends PropertyReference> getPropertyRefs(
SubpropertiesMirror subpropertiesMirror);
}
private final Subproperties subproperties = new Subproperties() {
@Override
List<? extends PropertyReference> getPropertyRefs(SubpropertiesMirror subpropertiesMirror) {
return subpropertiesMirror.getProperties();
}
};
private final Subproperties internalProperties = new Subproperties() {
@Override
List<? extends PropertyReference> getPropertyRefs(SubpropertiesMirror subpropertiesMirror) {
return subpropertiesMirror.getInternalProperties();
}
};
}