blob: 637ed8266707ce9a8e3d4aa14dd7f05dddd84d98 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 xored software, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* xored software, Inc. - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.internal.javascript.ti;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.dltk.javascript.core.JavaScriptPlugin;
import org.eclipse.dltk.javascript.core.Types;
import org.eclipse.dltk.javascript.typeinference.IValueCollection;
import org.eclipse.dltk.javascript.typeinference.IValueReference;
import org.eclipse.dltk.javascript.typeinference.ReferenceKind;
import org.eclipse.dltk.javascript.typeinference.ReferenceLocation;
import org.eclipse.dltk.javascript.typeinfo.IRProperty;
import org.eclipse.dltk.javascript.typeinfo.IRSimpleType;
import org.eclipse.dltk.javascript.typeinfo.IRType;
import org.eclipse.dltk.javascript.typeinfo.ITypeSystem;
import org.eclipse.dltk.javascript.typeinfo.JSTypeSet;
import org.eclipse.dltk.javascript.typeinfo.ReferenceSource;
import org.eclipse.dltk.javascript.typeinfo.model.Type;
public class Value extends ImmutableValue {
public Value() {
}
public Value(ImmutableValue value) {
super(value);
}
@Override
public void setDeclaredType(IRType declaredType) {
this.declaredType = declaredType;
}
@Override
public void addType(IRType type) {
if (type != null) {
this.types.add(type);
}
}
@Override
public void setKind(ReferenceKind kind) {
this.kind = kind;
}
@Override
public void setLocation(ReferenceLocation location) {
this.location = location;
}
@Override
public void setAttribute(String key, Object value) {
if (value != null) {
if (attributes == null) {
attributes = new HashMap<String, Object>();
}
attributes.put(key, value);
} else {
if (attributes != null) {
attributes.remove(key);
}
}
}
@Override
public void deleteChild(String name, boolean force) {
if (force) {
children.remove(name);
inherited.remove(name);
} else {
if (deletedChildren == null) {
deletedChildren = new HashSet<String>();
}
deletedChildren.add(name);
}
}
@Override
public void putChild(String name, IValue value) {
inherited.put(name, value);
}
private static class CreateChildOperation implements Handler<Set<IValue>> {
private final String childName;
public CreateChildOperation(String childName) {
this.childName = childName;
}
public void process(ImmutableValue value, Set<IValue> result) {
if (result.isEmpty() && !value.hasReferences()) {
IValue child = value.createChild(childName, 0);
if (child != null)
result.add(child);
}
}
}
@Override
public IValue createChild(String name, int flags) {
IValue child = children.get(name);
if (child == null) {
child = inherited.get(name);
if (child == null) {
if ((flags & CREATE) == 0) {
// creating new child, so ignore external elements
child = findMember(name, false);
if (child != null) {
return child;
}
}
if (hasReferences()) {
Set<IValue> result = new HashSet<IValue>();
execute(this, new CreateChildOperation(name), result,
new HashSet<IValue>());
if (!result.isEmpty()) {
return result.iterator().next();
}
}
child = new Value();
children.put(name, (Value) child);
childCreated(name);
}
}
return child;
}
@Override
public void clear() {
references.clear();
children.clear();
inherited.clear();
types.clear();
}
@SuppressWarnings("serial")
static class DeepValueRecursionException extends RuntimeException {
}
private static final ThreadLocal<AtomicBoolean> recursionErrorReported = new ThreadLocal<AtomicBoolean>() {
@Override
protected AtomicBoolean initialValue() {
return new AtomicBoolean();
}
};
@Override
public void addValue(IValue src) {
if (src instanceof ImmutableValue) {
final IdentityHashMap<ImmutableValue, ImmutableValue> processing = new IdentityHashMap<ImmutableValue, ImmutableValue>();
try {
addValueRecursive((ImmutableValue) src, processing, 0);
} catch (DeepValueRecursionException e) {
e.printStackTrace();
if (recursionErrorReported.get().compareAndSet(false, true)) {
String msg = "Deep recursion while copying the value";
final ReferenceSource source = ITypeSystem.CURRENT
.getCurrentSource();
if (source != null) {
msg += " when processing " + source;
}
JavaScriptPlugin.error(msg, e);
}
}
// translate references, so they point to the new value
for (Map.Entry<ImmutableValue, ImmutableValue> entry : processing
.entrySet()) {
final ImmutableValue input = entry.getKey();
final ImmutableValue output = entry.getValue();
for (IValue ref : input.references) {
IValue refOut = processing.get(ref);
if (refOut == null) {
refOut = ref;
}
output.references.add(refOut);
}
}
} else {
// ElementValue is handled in this branch.
final IRType srcType = src.getDeclaredType();
if (srcType != null) {
types.add(srcType);
}
types.addAll(src.getTypes());
if (src.getKind() == ReferenceKind.METHOD) {
final Object element = src.getAttribute(
IReferenceAttributes.ELEMENT, false);
if (element != null) {
setAttribute(IReferenceAttributes.ELEMENT, element);
}
final IValue returnType = src.getChild(
IValueReference.FUNCTION_OP, false);
if (returnType != null) {
final JSTypeSet myReturnTypes = createChild(
IValueReference.FUNCTION_OP, 0).getTypes();
myReturnTypes.addAll(returnType.getTypes());
myReturnTypes.addAll(returnType.getDeclaredTypes());
}
} else if (src.getKind() == ReferenceKind.PROPERTY
&& !isPrimitiveValue(srcType)) {
/*
* to optimize memory usage remember *static non-primitive
* properties* only
*/
final Object element = src.getAttribute(
IReferenceAttributes.ELEMENT, false);
if (element != null && element instanceof IRProperty
&& ((IRProperty) element).isStatic()) {
setAttribute(IReferenceAttributes.ELEMENT, element);
}
}
}
}
private static boolean isPrimitiveValue(IRType type) {
if (type instanceof IRSimpleType) {
final Type target = ((IRSimpleType) type).getTarget();
return target == Types.BOOLEAN || target == Types.STRING
|| target == Types.NUMBER;
} else {
return false;
}
}
@Override
public void addReference(IValue src) {
assert src != null;
if (src == this)
return;
references.add(src);
}
@Override
public void removeReference(IValue value) {
references.remove(value);
}
private void addValueRecursive(ImmutableValue src,
Map<ImmutableValue, ImmutableValue> processing, int depth) {
if (!processing.containsKey(src)) {
processing.put(src, this);
if (depth > 8) {
throw new DeepValueRecursionException();
}
if (src.declaredType != null) {
types.add(src.declaredType);
} else {
types.addAll(src.types);
}
// (references will be copied later)
if (src.attributes != null) {
if (attributes == null) {
attributes = new HashMap<String, Object>();
}
attributes.putAll(src.attributes);
}
for (Map.Entry<String, ImmutableValue> entry : src.children
.entrySet()) {
IValue child = createChild(entry.getKey(), 0);
if (child instanceof Value) {
((Value) child).addValueRecursive(entry.getValue(),
processing, depth + 1);
}
}
if (src.kind != ReferenceKind.UNKNOWN
&& kind == ReferenceKind.UNKNOWN) {
kind = src.kind;
}
if (src.location != ReferenceLocation.UNKNOWN
&& location == ReferenceLocation.UNKNOWN) {
location = src.location;
} else if (src.location != ReferenceLocation.UNKNOWN
&& src.declaredType != null
&& src.kind == ReferenceKind.FUNCTION) {
// if src location is known and the src is a function, then we
// need to remember the location if this function is used as a
// local type (through the assignment)
if (attributes == null) {
attributes = new HashMap<String, Object>();
}
attributes.put(IReferenceAttributes.LOCAL_TYPE_LOCATION,
src.location);
}
}
}
public void putDirectChild(String name, ImmutableValue value) {
children.put(name, value);
}
public void copyChilds(ImmutableValue value) {
if (value.hasReferences()) {
execute(value, new Handler<Map<String, ImmutableValue>>() {
public void process(ImmutableValue value,
Map<String, ImmutableValue> result) {
result.putAll(value.children);
};
}, this.children, new HashSet<IValue>());
} else {
this.children.putAll(value.children);
}
}
public void resolveLazyValues(Set<Value> visited) {
if (visited.add(this)) {
for (IValue value : references) {
if (value instanceof ILazyValue
&& !((ILazyValue) value).isResolved()) {
((ILazyValue) value).setFinalResolve();
} else if (value instanceof Value) {
((Value) value).resolveLazyValues(visited);
}
}
for (ImmutableValue value : children.values()) {
if (value instanceof Value)
((Value) value).resolveLazyValues(visited);
}
if (attributes != null) {
for (Object attibute : attributes.values()) {
if (attibute instanceof IValueProvider) {
IValue value = ((IValueProvider) attibute).getValue();
if (value instanceof Value) {
((Value) value).resolveLazyValues(visited);
}
}
}
}
}
}
public ImmutableValue getImmutableValue(Map<Object, Object> visited) {
ImmutableValue immutableValue = (ImmutableValue) visited.get(this);
if (immutableValue != null)
return immutableValue;
if (this instanceof ILazyValue) {
((ILazyValue) this).resolve();
}
JSTypeSet typeSet = JSTypeSet.create();
typeSet.addAll(types);
Set<String> deletedChilds = null;
if (deletedChildren != null) {
deletedChilds = new HashSet<String>(deletedChildren.size(), 0.9f);
deletedChilds.addAll(deletedChildren);
}
Map<String, ImmutableValue> childs = new HashMap<String, ImmutableValue>(
children.size(), 0.9f);
Map<String, IValue> inherits = new HashMap<String, IValue>(
inherited.size(), 0.9f);
Set<IValue> refers = new HashSet<IValue>(references.size(), 0.9f);
Map<String, Object> atts = null;
if (attributes != null) {
atts = new HashMap<String, Object>(attributes.size(), 0.9f);
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
if (entry.getValue() instanceof Value) {
atts.put(entry.getKey(), ((Value) entry.getValue())
.getImmutableValue(visited));
} else if (entry.getValue() instanceof IValueCollection) {
atts.put(entry.getKey(), ImmutableValueCollection
.getImmutableValueCollection(
(IValueCollection) entry.getValue(),
visited));
} else {
atts.put(entry.getKey(), entry.getValue());
}
}
}
immutableValue = new ImmutableValue(declaredType, typeSet,
deletedChilds, kind, location, childs, inherits, refers, atts);
visited.put(this, immutableValue);
for (IValue value : references) {
if (value instanceof Value) {
refers.add(((Value) value).getImmutableValue(visited));
} else {
refers.add(value);
}
}
for (Map.Entry<String, ImmutableValue> entry : children.entrySet()) {
if (entry.getValue() instanceof Value) {
childs.put(entry.getKey(),
((Value) entry.getValue()).getImmutableValue(visited));
} else {
childs.put(entry.getKey(), entry.getValue());
}
}
for (Map.Entry<String, IValue> entry : inherited.entrySet()) {
if (entry.getValue() instanceof Value)
inherits.put(entry.getKey(),
((Value) entry.getValue()).getImmutableValue(visited));
else
inherits.put(entry.getKey(), entry.getValue());
}
return immutableValue;
}
}