| /******************************************************************************* |
| * 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; |
| } |
| |
| } |