diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java index 3b34d2c18..7e8752ec9 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java @@ -32,7 +32,6 @@ import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; -import org.graalvm.collections.EconomicMap; import org.pkl.core.PClassInfo; import org.pkl.core.SecurityManagerException; import org.pkl.core.TypeParameter; @@ -55,6 +54,7 @@ import org.pkl.core.ast.lambda.ApplyVmFunction1NodeGen; import org.pkl.core.ast.member.*; import org.pkl.core.ast.type.*; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.externalreader.ExternalReaderProcessException; import org.pkl.core.module.ModuleKey; import org.pkl.core.module.ModuleKeys; @@ -68,7 +68,6 @@ import org.pkl.core.stdlib.registry.ExternalMemberRegistry; import org.pkl.core.stdlib.registry.MemberRegistryFactory; import org.pkl.core.util.CollectionUtils; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; import org.pkl.core.util.Pair; @@ -217,7 +216,7 @@ public PklRootNode visitModule(ModuleContext ctx) { for (var methodCtx : ctx.ms) { var localMethod = doVisitObjectMethod(methodCtx.methodHeader(), methodCtx.expr(), true); - EconomicMaps.put(moduleProperties, localMethod.getName(), localMethod); + moduleProperties.put(localMethod.getName(), localMethod); } var moduleNode = @@ -302,7 +301,7 @@ public ObjectMember visitClazz(ClazzContext ctx) { typeParameters, null, supertypeNode, - EconomicMaps.create(), + EconomicMap.create(), doVisitClassProperties(propertyCtxs, propertyNames), doVisitMethodDefs(methodCtxs)); @@ -1065,7 +1064,7 @@ private ExpressionNode doVisitObjectBody(ObjectBodyContext ctx, ExpressionNode p var parametersDescriptorBuilder = createFrameDescriptorBuilder(ctx); var parameterTypes = doVisitParameterTypes(ctx); - var members = EconomicMaps.create(); + var members = EconomicMap.create(); var elements = new ArrayList(); var keyNodes = new ArrayList(); var values = new ArrayList(); @@ -1188,7 +1187,7 @@ private void addConstantEntries( for (var i = 0; i < keyNodes.size(); i++) { var key = ((ConstantNode) keyNodes.get(i)).getValue(); var value = values.get(i); - var previousValue = EconomicMaps.put(members, key, value); + var previousValue = members.put(key, value); if (previousValue != null) { CompilerDirectives.transferToInterpreter(); throw exceptionBuilder() @@ -1310,7 +1309,7 @@ public ExpressionNode visitAnnotation(AnnotationContext ctx) { currentScope.isCustomThisScope(), null, new UnresolvedTypeNode[0], - EconomicMaps.create(), + EconomicMap.create(), verifyNode); } @@ -2606,12 +2605,12 @@ private EconomicMap doVisitModuleProperties( ModuleInfo moduleInfo) { var totalSize = importCtxs.size() + classCtxs.size() + typeAliasCtxs.size(); - var result = EconomicMaps.create(totalSize); + var result = EconomicMap.create(totalSize); for (var ctx : importCtxs) { var member = visitImportClause(ctx); checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); + result.put(member.getName(), member); } for (var ctx : classCtxs) { @@ -2625,7 +2624,7 @@ private EconomicMap doVisitModuleProperties( } checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); + result.put(member.getName(), member); } for (TypeAliasContext ctx : typeAliasCtxs) { @@ -2639,7 +2638,7 @@ private EconomicMap doVisitModuleProperties( } checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); + result.put(member.getName(), member); } for (var ctx : propertyCtxs) { @@ -2660,7 +2659,7 @@ private EconomicMap doVisitModuleProperties( } checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames); - EconomicMaps.put(result, member.getName(), member); + result.put(member.getName(), member); } return result; @@ -2684,7 +2683,7 @@ private void checkDuplicateMember( // TODO: use Set and checkDuplicateMember() to find duplicates between local and non-local // properties private void addProperty(EconomicMap objectMembers, ObjectMember property) { - if (EconomicMaps.put(objectMembers, property.getName(), property) != null) { + if (objectMembers.put(property.getName(), property) != null) { throw exceptionBuilder() .evalError("duplicateDefinition", property.getName()) .withSourceSection(property.getHeaderSection()) diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorElementNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorElementNode.java index 82c41ab4c..356c97192 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorElementNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorElementNode.java @@ -24,7 +24,6 @@ import org.pkl.core.runtime.VmClass; import org.pkl.core.runtime.VmDynamic; import org.pkl.core.runtime.VmListing; -import org.pkl.core.util.EconomicMaps; @ImportStatic(BaseModule.class) public abstract class GeneratorElementNode extends GeneratorMemberNode { @@ -68,7 +67,7 @@ void fallback(Object parent, ObjectData data) { private void addElement(ObjectData data) { long index = data.length; - EconomicMaps.put(data.members, index, element); + data.members.put(index, element); data.length += 1; data.persistForBindings(index); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorEntryNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorEntryNode.java index 4aba77d18..72df8f676 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorEntryNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorEntryNode.java @@ -25,7 +25,6 @@ import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.runtime.*; import org.pkl.core.runtime.VmException.ProgramValue; -import org.pkl.core.util.EconomicMaps; @ImportStatic(BaseModule.class) public abstract class GeneratorEntryNode extends GeneratorMemberNode { @@ -112,7 +111,7 @@ private void addListingEntry(VirtualFrame frame, ObjectData data, int parentLeng } private void doAdd(Object key, ObjectData data) { - if (EconomicMaps.put(data.members, key, member) != null) { + if (data.members.put(key, member) != null) { CompilerDirectives.transferToInterpreter(); throw duplicateDefinition(key, member); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorMemberNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorMemberNode.java index a8e495ed9..f5dc9ed92 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorMemberNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorMemberNode.java @@ -22,14 +22,13 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; import java.util.Arrays; -import org.graalvm.collections.EconomicMap; import org.pkl.core.ast.PklNode; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmClass; import org.pkl.core.runtime.VmException; import org.pkl.core.runtime.VmException.ProgramValue; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; public abstract class GeneratorMemberNode extends PklNode { @@ -81,7 +80,7 @@ protected boolean checkIsValidTypedProperty(VmClass clazz, ObjectMember member) public static final class ObjectData { // member count is exact iff every for/when body has exactly one member ObjectData(int minMemberCount, int length) { - this.members = EconomicMaps.create(minMemberCount); + this.members = EconomicMap.create(minMemberCount); this.length = length; } @@ -122,7 +121,7 @@ public static final class ObjectData { } void persistForBindings(Object key) { - EconomicMaps.put(forBindings, key, currentForBindings); + forBindings.put(key, currentForBindings); } void resetForBindings(Object @Nullable [] bindings) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java index a6446a53e..fbf11d3a8 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPredicateMemberNode.java @@ -24,9 +24,8 @@ import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.builder.SymbolTable.CustomThisScope; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicSet; import org.pkl.core.runtime.*; -import org.pkl.core.util.EconomicMaps; -import org.pkl.core.util.EconomicSets; public abstract class GeneratorPredicateMemberNode extends GeneratorMemberNode { @Child private ExpressionNode predicateNode; @@ -81,15 +80,15 @@ private void addMembers(VirtualFrame frame, VmObject parent, ObjectData data) { initThisSlot(frame); var previousValue = frame.getAuxiliarySlot(customThisSlot); - var visitedKeys = EconomicSets.create(); + var visitedKeys = EconomicSet.create(); // do our own traversal instead of relying on `VmAbstractObject.force/iterateMemberValues` // (more efficient and we don't want to execute `predicateNode` behind Truffle boundary) for (var owner = parent; owner != null; owner = owner.getParent()) { - var entries = EconomicMaps.getEntries(owner.getMembers()); + var entries = owner.getMembers().getEntries(); while (entries.advance()) { var key = entries.getKey(); - if (!EconomicSets.add(visitedKeys, key)) continue; + if (!visitedKeys.add(key)) continue; var member = entries.getValue(); if (member.isProp() || member.isLocal()) continue; @@ -136,7 +135,7 @@ private void initThisSlot(VirtualFrame frame) { } private void doAdd(Object key, ObjectData data) { - if (EconomicMaps.put(data.members, key, member) != null) { + if (data.members.put(key, member) != null) { CompilerDirectives.transferToInterpreter(); throw duplicateDefinition(key, member); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPropertyNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPropertyNode.java index bb24b82b6..aefb2bb33 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPropertyNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorPropertyNode.java @@ -22,7 +22,6 @@ import com.oracle.truffle.api.dsl.Specialization; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.runtime.*; -import org.pkl.core.util.EconomicMaps; @ImportStatic({BaseModule.class, GeneratorObjectLiteralNode.class}) public abstract class GeneratorPropertyNode extends GeneratorMemberNode { @@ -118,7 +117,7 @@ protected boolean checkIsValidMappingProperty() { } private void addProperty(ObjectData data) { - if (EconomicMaps.put(data.members, member.getName(), member) == null) return; + if (data.members.put(member.getName(), member) == null) return; CompilerDirectives.transferToInterpreter(); throw duplicateDefinition(member.getName(), member); diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorSpreadNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorSpreadNode.java index fb8945b40..5a3ac4fd3 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorSpreadNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorSpreadNode.java @@ -41,7 +41,6 @@ import org.pkl.core.runtime.VmObject; import org.pkl.core.runtime.VmTyped; import org.pkl.core.runtime.VmUtils; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.MutableLong; @ImportStatic(BaseModule.class) @@ -179,9 +178,9 @@ protected void doEvalDynamic(ObjectData data, VmObject iterable) { iterable.forceAndIterateMemberValues( (key, member, value) -> { if (member.isElement()) { - EconomicMaps.put(data.members, length.getAndIncrement(), createMember(member, value)); + data.members.put(length.getAndIncrement(), createMember(member, value)); } else { - if (EconomicMaps.put(data.members, key, createMember(member, value)) != null) { + if (data.members.put(key, createMember(member, value)) != null) { duplicateMember(key, member); } } @@ -196,7 +195,7 @@ private void doEvalMapping(ObjectData data, VmObject iterable) { if (member.isElement() || member.isProp()) { cannotHaveMember(BaseModule.getMappingClass(), member); } - if (EconomicMaps.put(data.members, key, createMember(member, value)) != null) { + if (data.members.put(key, createMember(member, value)) != null) { duplicateMember(key, member); } return true; @@ -210,7 +209,7 @@ private void doEvalListing(ObjectData data, VmObject iterable) { if (member.isEntry() || member.isProp()) { cannotHaveMember(getListingClass(), member); } - EconomicMaps.put(data.members, length.getAndIncrement(), createMember(member, value)); + data.members.put(length.getAndIncrement(), createMember(member, value)); return true; }); data.length = (int) length.get(); @@ -223,7 +222,7 @@ private void doEvalTyped(VmClass clazz, ObjectData data, VmObject iterable) { cannotHaveMember(clazz, member); } checkTypedProperty(clazz, member); - if (EconomicMaps.put(data.members, key, createMember(member, value)) != null) { + if (data.members.put(key, createMember(member, value)) != null) { duplicateMember(key, member); } return true; @@ -255,7 +254,7 @@ private void doEvalMap(VmClass parent, ObjectData data, VmMap iterable) { } for (var entry : iterable) { var member = VmUtils.createSyntheticObjectEntry("", VmUtils.getValue(entry)); - if (EconomicMaps.put(data.members, VmUtils.getKey(entry), member) != null) { + if (data.members.put(VmUtils.getKey(entry), member) != null) { duplicateMember(VmUtils.getKey(entry), member); } } @@ -337,7 +336,7 @@ private void spreadIterable(ObjectData data, Iterable iterable) { for (var elem : iterable) { var index = length++; var member = VmUtils.createSyntheticObjectElement(String.valueOf(index), elem); - EconomicMaps.put(data.members, (long) index, member); + data.members.put((long) index, member); } data.length = length; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/WriteForVariablesNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/WriteForVariablesNode.java index 23659a424..4397f81e8 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/WriteForVariablesNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/WriteForVariablesNode.java @@ -17,10 +17,9 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.ExpressionNode; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.runtime.VmUtils; -import org.pkl.core.util.EconomicMaps; public final class WriteForVariablesNode extends ExpressionNode { private final int[] auxiliarySlots; @@ -39,7 +38,7 @@ public Object executeGeneric(VirtualFrame frame) { @SuppressWarnings("unchecked") var forBindings = (UnmodifiableEconomicMap) extraStorage; - var bindings = EconomicMaps.get(forBindings, VmUtils.getMemberKey(frame)); + var bindings = forBindings.get(VmUtils.getMemberKey(frame)); assert bindings != null; assert bindings.length == auxiliarySlots.length; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java index 33da5e19e..d80e83fe2 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/AmendModuleNode.java @@ -20,10 +20,10 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; -import org.graalvm.collections.EconomicMap; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.runtime.ModuleInfo; import org.pkl.core.runtime.VmLanguage; import org.pkl.core.runtime.VmTyped; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ConstantEntriesLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ConstantEntriesLiteralNode.java index af481fba7..ed019c3e9 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ConstantEntriesLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ConstantEntriesLiteralNode.java @@ -21,10 +21,10 @@ import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.runtime.*; import org.pkl.core.util.Nullable; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsEntriesLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsEntriesLiteralNode.java index 2209b1536..cf02f9711 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsEntriesLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsEntriesLiteralNode.java @@ -24,12 +24,12 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.source.SourceSection; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.runtime.*; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; /** @@ -152,15 +152,15 @@ protected void fallback(Object parent) { protected UnmodifiableEconomicMap createMembers( VirtualFrame frame, int parentLength) { var result = - EconomicMaps.create( - EconomicMaps.size(members) + keyNodes.length + elements.length); + EconomicMap.create( + members.size() + keyNodes.length + elements.length); - EconomicMaps.putAll(result, members); + result.putAll(members); addListEntries(frame, parentLength, result, keyNodes, values); for (var i = 0; i < elements.length; i++) { - EconomicMaps.put(result, (long) (parentLength + i), elements[i]); + result.put((long) (parentLength + i), elements[i]); } return result; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsLiteralNode.java index ffaaa0800..913ba070d 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ElementsLiteralNode.java @@ -20,12 +20,12 @@ import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.runtime.*; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; /** @@ -170,11 +170,10 @@ protected void fallback(Object parent) { // offset element keys according to parentLength protected UnmodifiableEconomicMap createMembers(int parentLength) { - var result = - EconomicMaps.create(EconomicMaps.size(members) + elements.length); - EconomicMaps.putAll(result, members); + var result = EconomicMap.create(members.size() + elements.length); + result.putAll(members); for (var i = 0; i < elements.length; i++) { - EconomicMaps.put(result, (long) (parentLength + i), elements[i]); + result.put((long) (parentLength + i), elements[i]); } return result; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EntriesLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EntriesLiteralNode.java index f7a05ec84..ea86fa4e6 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EntriesLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EntriesLiteralNode.java @@ -22,14 +22,13 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.source.SourceSection; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.runtime.*; import org.pkl.core.runtime.VmException.ProgramValue; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; /** @@ -157,14 +156,13 @@ protected Object fallback(Object parent) { @ExplodeLoop protected EconomicMap createMapMembers(VirtualFrame frame) { - var result = - EconomicMaps.create(EconomicMaps.size(members) + keyNodes.length); - EconomicMaps.putAll(result, members); + var result = EconomicMap.create(members.size() + keyNodes.length); + result.putAll(members); for (var i = 0; i < keyNodes.length; i++) { var key = keyNodes[i].executeGeneric(frame); var value = values[i]; - var previousValue = EconomicMaps.put(result, key, value); + var previousValue = result.put(key, value); if (previousValue != null) { CompilerDirectives.transferToInterpreter(); throw exceptionBuilder() @@ -179,9 +177,8 @@ protected EconomicMap createMapMembers(VirtualFrame frame) protected UnmodifiableEconomicMap createListMembers( VirtualFrame frame, int parentLength) { - var result = - EconomicMaps.create(EconomicMaps.size(members) + keyNodes.length); - EconomicMaps.putAll(result, members); + var result = EconomicMap.create(members.size() + keyNodes.length); + result.putAll(members); addListEntries(frame, parentLength, result, keyNodes, values); return result; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/PropertiesLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/PropertiesLiteralNode.java index 8a51b46a8..5adfff166 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/PropertiesLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/PropertiesLiteralNode.java @@ -22,10 +22,10 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.source.SourceSection; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.runtime.*; import org.pkl.core.util.Nullable; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java index c825435a4..5c99133aa 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java @@ -24,14 +24,13 @@ import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.source.SourceSection; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.runtime.*; import org.pkl.core.runtime.VmException.ProgramValue; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; /** @@ -74,7 +73,7 @@ protected boolean checkIsValidTypedAmendment(Object parent) { var parentClass = parent instanceof VmClass vmClass ? vmClass : VmUtils.getClass(parent); VmUtils.checkIsInstantiable(parentClass, getParentNode()); - for (var member : EconomicMaps.getValues(members)) { + for (var member : members.getValues()) { if (member.isLocal()) continue; var memberName = member.getName(); @@ -119,7 +118,7 @@ protected boolean checkIsValidTypedAmendment(Object parent) { protected final boolean checkIsValidListingAmendment() { if (maxListingMemberIndex != Long.MIN_VALUE) return true; - var cursor = EconomicMaps.getEntries(members); + var cursor = members.getEntries(); long maxIndex = -1; while (cursor.advance()) { @@ -172,7 +171,7 @@ protected final boolean checkIsValidListingAmendment() { protected final boolean checkIsValidMappingAmendment() { if (checkedIsValidMappingAmendment) return true; - for (var member : EconomicMaps.getValues(members)) { + for (var member : members.getValues()) { if (member.isLocal()) continue; var memberName = member.getNameOrNull(); @@ -203,7 +202,7 @@ protected final boolean checkMaxListingMemberIndex(int parentLength) { if (maxListingMemberIndex < parentLength) return true; CompilerDirectives.transferToInterpreter(); - var cursor = EconomicMaps.getEntries(members); + var cursor = members.getEntries(); while (cursor.advance()) { var key = cursor.getKey(); if (!(key instanceof Long)) continue; @@ -251,7 +250,7 @@ protected void addListEntries( .build(); } - if (EconomicMaps.put(result, index, value) != null) { + if (result.put(index, value) != null) { CompilerDirectives.transferToInterpreter(); throw exceptionBuilder() .evalError("duplicateDefinition", new ProgramValue("", index)) @@ -264,7 +263,7 @@ protected void addListEntries( @TruffleBoundary protected @Nullable ObjectMember findFirstNonProperty( UnmodifiableEconomicMap members) { - var cursor = EconomicMaps.getEntries(members); + var cursor = members.getEntries(); while (cursor.advance()) { var member = cursor.getValue(); if (member.getNameOrNull() == null) return member; @@ -275,7 +274,7 @@ protected void addListEntries( @TruffleBoundary protected @Nullable ObjectMember findFirstNonDefaultProperty( UnmodifiableEconomicMap members) { - var cursor = EconomicMaps.getEntries(members); + var cursor = members.getEntries(); while (cursor.advance()) { var member = cursor.getValue(); if (member.getNameOrNull() != null && member.getNameOrNull() != Identifier.DEFAULT) diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ReadGlobNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ReadGlobNode.java index 9331629bc..afb17f9d7 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ReadGlobNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/unary/ReadGlobNode.java @@ -23,9 +23,9 @@ import com.oracle.truffle.api.source.SourceSection; import java.io.IOException; import java.net.URISyntaxException; -import org.graalvm.collections.EconomicMap; import org.pkl.core.SecurityManagerException; import org.pkl.core.ast.member.SharedMemberNode; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.externalreader.ExternalReaderProcessException; import org.pkl.core.http.HttpClientInitException; import org.pkl.core.module.ModuleKey; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java index 255642dcc..86911e0cb 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ClassNode.java @@ -22,12 +22,12 @@ import com.oracle.truffle.api.source.SourceSection; import java.util.ArrayList; import java.util.List; -import org.graalvm.collections.EconomicMap; import org.pkl.core.PClassInfo; import org.pkl.core.TypeParameter; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.type.TypeNode; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.runtime.*; import org.pkl.core.util.LateInit; import org.pkl.core.util.Nullable; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java index 149b43671..0fbcd0e66 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java @@ -43,9 +43,9 @@ import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.member.UntypedObjectMemberNode; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.EconomicSet; import org.pkl.core.runtime.*; -import org.pkl.core.util.EconomicMaps; -import org.pkl.core.util.EconomicSets; import org.pkl.core.util.LateInit; import org.pkl.core.util.MutableBoolean; import org.pkl.core.util.Nonnull; @@ -897,7 +897,7 @@ protected boolean acceptTypeNode(TypeNodeConsumer consumer) { */ @TruffleBoundary private boolean shouldEagerCheck() { - var seenParameterizedClasses = EconomicSets.create(); + var seenParameterizedClasses = EconomicSet.create(); var ret = new MutableBoolean(false); this.acceptTypeNode( (typeNode) -> { @@ -912,7 +912,7 @@ private boolean shouldEagerCheck() { ret.set(true); return false; } else { - EconomicSets.add(seenParameterizedClasses, typeNodeClass); + seenParameterizedClasses.add(typeNodeClass); return true; } }); @@ -1587,14 +1587,14 @@ public final Object createDefaultValue( return new VmListing( VmUtils.createEmptyMaterializedFrame(), BaseModule.getListingClass().getPrototype(), - EconomicMaps.create(), + EconomicMap.create(), 0); } return new VmMapping( VmUtils.createEmptyMaterializedFrame(), BaseModule.getMappingClass().getPrototype(), - EconomicMaps.create()); + EconomicMap.create()); } var defaultMember = @@ -1637,14 +1637,14 @@ public final Object createDefaultValue( return new VmListing( VmUtils.createEmptyMaterializedFrame(), BaseModule.getListingClass().getPrototype(), - EconomicMaps.of(Identifier.DEFAULT, defaultMember), + EconomicMap.of(Identifier.DEFAULT, defaultMember), 0); } return new VmMapping( VmUtils.createEmptyMaterializedFrame(), BaseModule.getMappingClass().getPrototype(), - EconomicMaps.of(Identifier.DEFAULT, defaultMember)); + EconomicMap.of(Identifier.DEFAULT, defaultMember)); } protected > T doTypeCast(VirtualFrame frame, T original) { @@ -1671,7 +1671,7 @@ protected void doEagerCheck( // similar to shallow forcing for (var owner = object; owner != null; owner = owner.getParent()) { - var cursor = EconomicMaps.getEntries(owner.getMembers()); + var cursor = owner.getMembers().getEntries(); while (cursor.advance()) { loopCount += 1; var member = cursor.getValue(); diff --git a/pkl-core/src/main/java/org/pkl/core/collection/EconomicMap.java b/pkl-core/src/main/java/org/pkl/core/collection/EconomicMap.java new file mode 100644 index 000000000..5f6b5f934 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/EconomicMap.java @@ -0,0 +1,275 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import java.util.Map; +import java.util.function.BiFunction; +import org.pkl.core.util.Nullable; + +/** + * Memory efficient map data structure that dynamically changes its representation depending on the + * number of entries and is specially optimized for small number of entries. It keeps elements in a + * linear list without any hashing when the number of entries is small. Should an actual hash data + * structure be necessary, it tries to fit the hash value into as few bytes as possible. In contrast + * to {@link java.util.HashMap}, it avoids allocating an extra node object per entry and rather + * keeps values always in a plain array. See {@link EconomicMapImpl} for implementation details and + * exact thresholds when its representation changes. + * + *

It supports a {@code null} value, but it does not support adding or looking up a {@code null} + * key. Operations {@code get} and {@code put} provide constant-time performance on average if + * repeatedly performed. They can however trigger an operation growing or compressing the data + * structure, which is linear in the number of elements. Iteration is also linear in the number of + * elements. + * + *

The implementation is not synchronized. If multiple threads want to access the data structure, + * it requires manual synchronization, for example using {@link + * java.util.Collections#synchronizedMap}. There is also no extra precaution to detect concurrent + * modification while iterating. + * + *

Different strategies for the equality comparison can be configured by providing a {@link + * Equivalence} configuration object. + * + * @since 19.0 + */ +public interface EconomicMap extends UnmodifiableEconomicMap { + + /** + * Associates {@code value} with {@code key} in this map. If the map previously contained a + * mapping for {@code key}, the old value is replaced by {@code value}. While the {@code value} + * may be {@code null}, the {@code key} must not be {code null}. + * + * @return the previous value associated with {@code key}, or {@code null} if there was no mapping + * for {@code key}. + * @since 19.0 + */ + @Nullable + V put(K key, @Nullable V value); + + /** + * If the specified key is not already associated with a value (or is mapped to {@code null}) + * associates it with the given value and returns {@code null}, else returns the current value. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with the specified key, or {@code null} if there was no + * mapping for the key. (A {@code null} return can also indicate that the map previously + * associated {@code null} with the key, if the implementation supports null values.) + * @since 20.2 + */ + @TruffleBoundary + default @Nullable V putIfAbsent(K key, @Nullable V value) { + V v = get(key); + if (v == null) { + v = put(key, value); + } + + return v; + } + + /** + * Copies all of the mappings from {@code other} to this map. + * + * @since 19.0 + */ + @TruffleBoundary + default void putAll(EconomicMap other) { + MapCursor e = other.getEntries(); + while (e.advance()) { + put(e.getKey(), e.getValue()); + } + } + + /** + * Copies all of the mappings from {@code other} to this map. + * + * @since 19.0 + */ + @TruffleBoundary + default void putAll(UnmodifiableEconomicMap other) { + UnmodifiableMapCursor entry = other.getEntries(); + while (entry.advance()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Removes all of the mappings from this map. The map will be empty after this call returns. + * + * @since 19.0 + */ + @TruffleBoundary + void clear(); + + /** + * Removes the mapping for {@code key} from this map if it is present. The map will not contain a + * mapping for {@code key} once the call returns. The {@code key} must not be {@code null}. + * + * @return the previous value associated with {@code key}, or {@code null} if there was no mapping + * for {@code key}. + * @since 19.0 + */ + @TruffleBoundary + @Nullable + V removeKey(K key); + + /** + * Returns a {@link MapCursor} view of the mappings contained in this map. + * + * @since 19.0 + */ + @Override + @TruffleBoundary + MapCursor getEntries(); + + /** + * Replaces each entry's value with the result of invoking {@code function} on that entry until + * all entries have been processed or the function throws an exception. Exceptions thrown by the + * function are relayed to the caller. + * + * @since 19.0 + */ + void replaceAll(BiFunction function); + + /** + * Creates a new map that guarantees insertion order on the key set with the default {@link + * Equivalence#DEFAULT} comparison strategy for keys. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicMap create() { + return EconomicMap.create(Equivalence.DEFAULT); + } + + /** + * Creates a new map that guarantees insertion order on the key set with the default {@link + * Equivalence#DEFAULT} comparison strategy for keys and initializes with a specified capacity. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicMap create(int initialCapacity) { + return EconomicMap.create(Equivalence.DEFAULT, initialCapacity); + } + + /** + * Creates a new map that guarantees insertion order on the key set with the given comparison + * strategy for keys. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicMap create(Equivalence strategy) { + return EconomicMapImpl.create(strategy, false); + } + + /** + * Creates a new map that guarantees insertion order on the key set with the default {@link + * Equivalence#DEFAULT} comparison strategy for keys and copies all elements from the specified + * existing map. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicMap create(UnmodifiableEconomicMap m) { + return EconomicMap.create(Equivalence.DEFAULT, m); + } + + /** + * Creates a new map that guarantees insertion order on the key set and copies all elements from + * the specified existing map. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicMap create(Equivalence strategy, UnmodifiableEconomicMap m) { + return EconomicMapImpl.create(strategy, m, false); + } + + /** + * Creates a new map that guarantees insertion order on the key set and initializes with a + * specified capacity. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicMap create(Equivalence strategy, int initialCapacity) { + return EconomicMapImpl.create(strategy, initialCapacity, false); + } + + /** + * Wraps an existing {@link Map} as an {@link EconomicMap}. + * + * @since 19.0 + */ + static EconomicMap wrapMap(Map map) { + return new EconomicMapWrap<>(map); + } + + /** + * Return an empty {@link MapCursor}. + * + * @since 22.0 + */ + @SuppressWarnings("unchecked") + static MapCursor emptyCursor() { + return (MapCursor) EmptyMap.EMPTY_CURSOR; + } + + /** + * Return an empty, unmodifiable {@link EconomicMap}. + * + * @since 22.2 + */ + @SuppressWarnings("unchecked") + static EconomicMap emptyMap() { + return (EconomicMap) EmptyMap.EMPTY_MAP; + } + + /** + * Creates an {@link EconomicMap} with one mapping. + * + * @param key1 the key of the first mapping + * @param value1 the value of the first mapping + * @return a map with the mapping + * @since 23.0 + */ + @TruffleBoundary + static EconomicMap of(K key1, V value1) { + EconomicMap map = EconomicMap.create(1); + map.put(key1, value1); + return map; + } + + /** + * Creates an {@link EconomicMap} with two mappings. + * + * @param key1 the key of the first mapping + * @param value1 the value of the first mapping + * @param key2 the key of the second mapping + * @param value2 the value of the second mapping + * @return a map with two mappings + * @since 23.0 + */ + @TruffleBoundary + static EconomicMap of(K key1, V value1, K key2, V value2) { + EconomicMap map = EconomicMap.create(2); + map.put(key1, value1); + map.put(key2, value2); + return map; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/EconomicMapImpl.java b/pkl-core/src/main/java/org/pkl/core/collection/EconomicMapImpl.java new file mode 100644 index 000000000..dd10e1d4d --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/EconomicMapImpl.java @@ -0,0 +1,869 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import java.util.Iterator; +import java.util.function.BiFunction; +import org.pkl.core.util.Nullable; + +/** + * Implementation of a map with a memory-efficient structure that always preserves insertion order + * when iterating over keys. Particularly efficient when number of entries is 0 or smaller equal + * {@link #INITIAL_CAPACITY} or smaller 256. + * + *

The key/value pairs are kept in an expanding flat object array with keys at even indices and + * values at odd indices. If the map has smaller or equal to {@link #HASH_THRESHOLD} entries, there + * is no additional hash data structure and comparisons are done via linear checking of the + * key/value pairs. For the case where the equality check is particularly cheap (e.g., just an + * object identity comparison), this limit below which the map is without an actual hash table is + * higher and configured at {@link #HASH_THRESHOLD_IDENTITY_COMPARE}. + * + *

When the hash table needs to be constructed, the field {@link #hashArray} becomes a new hash + * array where an entry of 0 means no hit and otherwise denotes the entry number in the {@link + * #entries} array. The hash array is interpreted as an actual byte array if the indices fit within + * 8 bit, or as an array of short values if the indices fit within 16 bit, or as an array of integer + * values in other cases. + * + *

Hash collisions are handled by chaining a linked list of {@link CollisionLink} objects that + * take the place of the values in the {@link #entries} array. + * + *

Removing entries will put {@code null} into the {@link #entries} array. If the occupation of + * the map falls below a specific threshold, the map will be compressed via the {@link + * #maybeCompress(int)} method. + */ +final class EconomicMapImpl implements EconomicMap, EconomicSet { + + /** Initial number of key/value pair entries that is allocated in the first entries array. */ + private static final int INITIAL_CAPACITY = 4; + + /** Maximum number of entries that are moved linearly forward if a key is removed. */ + private static final int COMPRESS_IMMEDIATE_CAPACITY = 8; + + /** Minimum number of key/value pair entries added when the entries array is increased in size. */ + private static final int MIN_CAPACITY_INCREASE = 8; + + /** Number of entries above which a hash table is created. */ + private static final int HASH_THRESHOLD = 4; + + /** + * Number of entries above which a hash table is created when equality can be checked with object + * identity. + */ + private static final int HASH_THRESHOLD_IDENTITY_COMPARE = 8; + + /** Maximum number of entries allowed in the map. */ + private static final int MAX_ELEMENT_COUNT = Integer.MAX_VALUE >> 1; + + /** Number of entries above which more than 1 byte is necessary for the hash index. */ + private static final int LARGE_HASH_THRESHOLD = ((1 << Byte.SIZE) << 1); + + /** Number of entries above which more than 2 bytes are are necessary for the hash index. */ + private static final int VERY_LARGE_HASH_THRESHOLD = (LARGE_HASH_THRESHOLD << Byte.SIZE); + + /** Total number of entries (actual entries plus deleted entries). */ + private int totalEntries; + + /** Number of deleted entries. */ + private int deletedEntries; + + /** Entries array with even indices storing keys and odd indices storing values. */ + private Object[] entries; + + /** + * Hash array that is interpreted either as byte or short or int array depending on number of map + * entries. + */ + private byte[] hashArray; + + /** + * The strategy used for comparing keys or {@code null} for denoting special strategy {@link + * Equivalence#IDENTITY}. + */ + private final Equivalence strategy; + + /** Intercept method for debugging purposes. */ + private static EconomicMapImpl intercept(EconomicMapImpl map) { + return map; + } + + @TruffleBoundary + public static EconomicMapImpl create(Equivalence strategy, boolean isSet) { + return intercept(new EconomicMapImpl<>(strategy, isSet)); + } + + @TruffleBoundary + public static EconomicMapImpl create( + Equivalence strategy, int initialCapacity, boolean isSet) { + return intercept(new EconomicMapImpl<>(strategy, initialCapacity, isSet)); + } + + @TruffleBoundary + public static EconomicMapImpl create( + Equivalence strategy, UnmodifiableEconomicMap other, boolean isSet) { + return intercept(new EconomicMapImpl<>(strategy, other, isSet)); + } + + @TruffleBoundary + public static EconomicMapImpl create( + Equivalence strategy, UnmodifiableEconomicSet other, boolean isSet) { + return intercept(new EconomicMapImpl<>(strategy, other, isSet)); + } + + private EconomicMapImpl(Equivalence strategy, boolean isSet) { + if (strategy == Equivalence.IDENTITY) { + this.strategy = null; + } else { + this.strategy = strategy; + } + this.isSet = isSet; + } + + private EconomicMapImpl(Equivalence strategy, int initialCapacity, boolean isSet) { + this(strategy, isSet); + init(initialCapacity); + } + + private EconomicMapImpl( + Equivalence strategy, UnmodifiableEconomicMap other, boolean isSet) { + this(strategy, isSet); + if (!initFrom(other)) { + init(other.size()); + putAll(other); + } + } + + private EconomicMapImpl(Equivalence strategy, UnmodifiableEconomicSet other, boolean isSet) { + this(strategy, isSet); + if (!initFrom(other)) { + init(other.size()); + addAll(other); + } + } + + @SuppressWarnings("unchecked") + private boolean initFrom(Object o) { + if (o instanceof EconomicMapImpl) { + EconomicMapImpl otherMap = (EconomicMapImpl) o; + // We are only allowed to directly copy if the strategies of the two maps are the same. + if (strategy == otherMap.strategy) { + totalEntries = otherMap.totalEntries; + deletedEntries = otherMap.deletedEntries; + if (otherMap.entries != null) { + entries = otherMap.entries.clone(); + } + if (otherMap.hashArray != null) { + hashArray = otherMap.hashArray.clone(); + } + return true; + } + } + return false; + } + + private void init(int size) { + if (size > INITIAL_CAPACITY) { + entries = new Object[size << 1]; + } + } + + /** + * Links the collisions. Needs to be immutable class for allowing efficient shallow copy from + * other map on construction. + */ + private static final class CollisionLink { + + CollisionLink(Object value, int next) { + this.value = value; + this.next = next; + } + + final Object value; + + /** Index plus one of the next entry in the collision link chain. */ + final int next; + } + + @SuppressWarnings("unchecked") + @Override + @TruffleBoundary + public V get(K key) { + checkKeyNonNull(key); + + int index = find(key); + if (index != -1) { + return (V) getValue(index); + } + return null; + } + + private int find(K key) { + if (hasHashArray()) { + return findHash(key); + } else { + return findLinear(key); + } + } + + private int findLinear(K key) { + for (int i = 0; i < totalEntries; i++) { + Object entryKey = entries[i << 1]; + if (entryKey != null && compareKeys(key, entryKey)) { + return i; + } + } + return -1; + } + + private boolean compareKeys(Object key, @Nullable Object entryKey) { + if (key == entryKey) { + return true; + } + if (strategy != null && strategy != Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE) { + if (strategy == Equivalence.DEFAULT) { + return key.equals(entryKey); + } else { + return strategy.equals(key, entryKey); + } + } + return false; + } + + private int findHash(K key) { + int index = getHashArray(getHashIndex(key)) - 1; + if (index != -1) { + Object entryKey = getKey(index); + if (compareKeys(key, entryKey)) { + return index; + } else { + Object entryValue = getRawValue(index); + if (entryValue instanceof CollisionLink) { + return findWithCollision(key, (CollisionLink) entryValue); + } + } + } + + return -1; + } + + private int findWithCollision(K key, CollisionLink initialEntryValue) { + int index; + Object entryKey; + CollisionLink entryValue = initialEntryValue; + while (true) { + CollisionLink collisionLink = entryValue; + index = collisionLink.next; + entryKey = getKey(index); + if (compareKeys(key, entryKey)) { + return index; + } else { + Object value = getRawValue(index); + if (value instanceof CollisionLink) { + entryValue = (CollisionLink) getRawValue(index); + } else { + return -1; + } + } + } + } + + private int getHashArray(int index) { + if (entries.length < LARGE_HASH_THRESHOLD) { + return (hashArray[index] & 0xFF); + } else if (entries.length < VERY_LARGE_HASH_THRESHOLD) { + int adjustedIndex = index << 1; + return (hashArray[adjustedIndex] & 0xFF) | ((hashArray[adjustedIndex + 1] & 0xFF) << 8); + } else { + int adjustedIndex = index << 2; + return (hashArray[adjustedIndex] & 0xFF) + | ((hashArray[adjustedIndex + 1] & 0xFF) << 8) + | ((hashArray[adjustedIndex + 2] & 0xFF) << 16) + | ((hashArray[adjustedIndex + 3] & 0xFF) << 24); + } + } + + private void setHashArray(int index, int value) { + if (entries.length < LARGE_HASH_THRESHOLD) { + hashArray[index] = (byte) value; + } else if (entries.length < VERY_LARGE_HASH_THRESHOLD) { + int adjustedIndex = index << 1; + hashArray[adjustedIndex] = (byte) value; + hashArray[adjustedIndex + 1] = (byte) (value >> 8); + } else { + int adjustedIndex = index << 2; + hashArray[adjustedIndex] = (byte) value; + hashArray[adjustedIndex + 1] = (byte) (value >> 8); + hashArray[adjustedIndex + 2] = (byte) (value >> 16); + hashArray[adjustedIndex + 3] = (byte) (value >> 24); + } + } + + private int findAndRemoveHash(Object key) { + int hashIndex = getHashIndex(key); + int index = getHashArray(hashIndex) - 1; + if (index != -1) { + Object entryKey = getKey(index); + if (compareKeys(key, entryKey)) { + Object value = getRawValue(index); + int nextIndex = -1; + if (value instanceof CollisionLink) { + CollisionLink collisionLink = (CollisionLink) value; + nextIndex = collisionLink.next; + } + setHashArray(hashIndex, nextIndex + 1); + return index; + } else { + Object entryValue = getRawValue(index); + if (entryValue instanceof CollisionLink) { + return findAndRemoveWithCollision(key, (CollisionLink) entryValue, index); + } + } + } + + return -1; + } + + private int findAndRemoveWithCollision( + Object key, CollisionLink initialEntryValue, int initialIndexValue) { + int index; + Object entryKey; + CollisionLink entryValue = initialEntryValue; + int lastIndex = initialIndexValue; + while (true) { + CollisionLink collisionLink = entryValue; + index = collisionLink.next; + entryKey = getKey(index); + if (compareKeys(key, entryKey)) { + Object value = getRawValue(index); + if (value instanceof CollisionLink) { + CollisionLink thisCollisionLink = (CollisionLink) value; + setRawValue(lastIndex, new CollisionLink(collisionLink.value, thisCollisionLink.next)); + } else { + setRawValue(lastIndex, collisionLink.value); + } + return index; + } else { + Object value = getRawValue(index); + if (value instanceof CollisionLink) { + entryValue = (CollisionLink) getRawValue(index); + lastIndex = index; + } else { + return -1; + } + } + } + } + + private int getHashIndex(Object key) { + int hash; + if (strategy != null && strategy != Equivalence.DEFAULT) { + if (strategy == Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE) { + hash = System.identityHashCode(key); + } else { + hash = strategy.hashCode(key); + } + } else { + hash = key.hashCode(); + } + hash = hash ^ (hash >>> 16); + return hash & (getHashTableSize() - 1); + } + + @SuppressWarnings("unchecked") + @Override + @TruffleBoundary + public V put(K key, V value) { + checkKeyNonNull(key); + int index = find(key); + if (index != -1) { + Object oldValue = getValue(index); + setValue(index, value); + return (V) oldValue; + } + + int nextEntryIndex = totalEntries; + if (entries == null) { + entries = new Object[INITIAL_CAPACITY << 1]; + } else if (entries.length == nextEntryIndex << 1) { + grow(); + + assert entries.length > totalEntries << 1; + // Can change if grow is actually compressing. + nextEntryIndex = totalEntries; + } + + setKey(nextEntryIndex, key); + setValue(nextEntryIndex, value); + totalEntries++; + + if (hasHashArray()) { + // Rehash on collision if hash table is more than three quarters full. + boolean rehashOnCollision = (getHashTableSize() < (size() + (size() >> 1))); + putHashEntry(key, nextEntryIndex, rehashOnCollision); + } else if (totalEntries > getHashThreshold()) { + createHash(); + } + + return null; + } + + /** Number of entries above which a hash table should be constructed. */ + private int getHashThreshold() { + if (strategy == null || strategy == Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE) { + return HASH_THRESHOLD_IDENTITY_COMPARE; + } else { + return HASH_THRESHOLD; + } + } + + private void grow() { + int entriesLength = entries.length; + int newSize = (entriesLength >> 1) + Math.max(MIN_CAPACITY_INCREASE, entriesLength >> 2); + if (newSize > MAX_ELEMENT_COUNT) { + throw new UnsupportedOperationException("map grown too large!"); + } + Object[] newEntries = new Object[newSize << 1]; + System.arraycopy(entries, 0, newEntries, 0, entriesLength); + entries = newEntries; + if ((entriesLength < LARGE_HASH_THRESHOLD && newEntries.length >= LARGE_HASH_THRESHOLD) + || (entriesLength < VERY_LARGE_HASH_THRESHOLD + && newEntries.length > VERY_LARGE_HASH_THRESHOLD)) { + // Rehash in order to change number of bits reserved for hash indices. + createHash(); + } + } + + /** + * Compresses the graph if there is a large number of deleted entries and returns the translated + * new next index. + */ + private int maybeCompress(int nextIndex) { + if (entries.length != INITIAL_CAPACITY << 1 + && deletedEntries >= (totalEntries >> 1) + (totalEntries >> 2)) { + return compressLarge(nextIndex); + } + return nextIndex; + } + + /** Compresses the graph and returns the translated new next index. */ + private int compressLarge(int nextIndex) { + int size = INITIAL_CAPACITY; + int remaining = totalEntries - deletedEntries; + + while (size <= remaining) { + size += Math.max(MIN_CAPACITY_INCREASE, size >> 1); + } + + Object[] newEntries = new Object[size << 1]; + int z = 0; + int newNextIndex = remaining; + for (int i = 0; i < totalEntries; ++i) { + Object key = getKey(i); + if (i == nextIndex) { + newNextIndex = z; + } + if (key != null) { + newEntries[z << 1] = key; + newEntries[(z << 1) + 1] = getValue(i); + z++; + } + } + + this.entries = newEntries; + totalEntries = z; + deletedEntries = 0; + if (z <= getHashThreshold()) { + this.hashArray = null; + } else { + createHash(); + } + return newNextIndex; + } + + private int getHashTableSize() { + if (entries.length < LARGE_HASH_THRESHOLD) { + return hashArray.length; + } else if (entries.length < VERY_LARGE_HASH_THRESHOLD) { + return hashArray.length >> 1; + } else { + return hashArray.length >> 2; + } + } + + private void createHash() { + int entryCount = size(); + + // Calculate smallest 2^n that is greater number of entries. + int size = getHashThreshold(); + while (size <= entryCount) { + size <<= 1; + } + + // Give extra size to avoid collisions. + size <<= 1; + + if (this.entries.length >= VERY_LARGE_HASH_THRESHOLD) { + // Every entry has 4 bytes. + size <<= 2; + } else if (this.entries.length >= LARGE_HASH_THRESHOLD) { + // Every entry has 2 bytes. + size <<= 1; + } else { + // Entries are very small => give extra size to further reduce collisions. + size <<= 1; + } + + hashArray = new byte[size]; + for (int i = 0; i < totalEntries; i++) { + Object entryKey = getKey(i); + if (entryKey != null) { + putHashEntry(entryKey, i, false); + } + } + } + + private void putHashEntry(Object key, int entryIndex, boolean rehashOnCollision) { + int hashIndex = getHashIndex(key); + int oldIndex = getHashArray(hashIndex) - 1; + if (oldIndex != -1 && rehashOnCollision) { + this.createHash(); + return; + } + setHashArray(hashIndex, entryIndex + 1); + Object value = getRawValue(entryIndex); + if (oldIndex != -1) { + assert entryIndex != oldIndex + : "this cannot happen and would create an endless collision link cycle"; + if (value instanceof CollisionLink) { + CollisionLink collisionLink = (CollisionLink) value; + setRawValue(entryIndex, new CollisionLink(collisionLink.value, oldIndex)); + } else { + setRawValue(entryIndex, new CollisionLink(getRawValue(entryIndex), oldIndex)); + } + } else { + if (value instanceof CollisionLink) { + CollisionLink collisionLink = (CollisionLink) value; + setRawValue(entryIndex, collisionLink.value); + } + } + } + + @Override + public int size() { + return totalEntries - deletedEntries; + } + + @Override + @TruffleBoundary + public boolean containsKey(K key) { + return find(key) != -1; + } + + @Override + public void clear() { + entries = null; + hashArray = null; + totalEntries = deletedEntries = 0; + } + + private boolean hasHashArray() { + return hashArray != null; + } + + @SuppressWarnings("unchecked") + @Override + @TruffleBoundary + public @Nullable V removeKey(K key) { + checkKeyNonNull(key); + int index; + if (hasHashArray()) { + index = this.findAndRemoveHash(key); + } else { + index = this.findLinear(key); + } + + if (index != -1) { + Object value = getValue(index); + remove(index); + return (V) value; + } + return null; + } + + private void checkKeyNonNull(K key) { + if (key == null) { + throw new UnsupportedOperationException("null not supported as key!"); + } + } + + /** + * Removes the element at the specific index and returns the index of the next element. This can + * be a different value if graph compression was triggered. + */ + private int remove(int indexToRemove) { + int index = indexToRemove; + int entriesAfterIndex = totalEntries - index - 1; + int result = index + 1; + + // Without hash array, compress immediately. + if (entriesAfterIndex <= COMPRESS_IMMEDIATE_CAPACITY && !hasHashArray()) { + while (index < totalEntries - 1) { + setKey(index, getKey(index + 1)); + setRawValue(index, getRawValue(index + 1)); + index++; + } + result--; + } + + setKey(index, null); + setRawValue(index, null); + if (index == totalEntries - 1) { + // Make sure last element is always non-null. + totalEntries--; + while (index > 0 && getKey(index - 1) == null) { + totalEntries--; + deletedEntries--; + index--; + } + } else { + deletedEntries++; + result = maybeCompress(result); + } + + return result; + } + + private abstract class SparseMapIterator implements Iterator { + + protected int current; + + @Override + public boolean hasNext() { + return current < totalEntries; + } + + @Override + @TruffleBoundary + public void remove() { + if (hasHashArray()) { + EconomicMapImpl.this.findAndRemoveHash(getKey(current - 1)); + } + current = EconomicMapImpl.this.remove(current - 1); + } + } + + @Override + @TruffleBoundary + public Iterable getValues() { + return new Iterable<>() { + @Override + public Iterator iterator() { + return new SparseMapIterator<>() { + @SuppressWarnings("unchecked") + @Override + public V next() { + Object result; + while (true) { + result = getValue(current); + if (result == null && getKey(current) == null) { + // values can be null, double-check if key is also null + current++; + } else { + current++; + break; + } + } + return (V) result; + } + }; + } + }; + } + + @Override + public Iterable getKeys() { + return this; + } + + @Override + public boolean isEmpty() { + return this.size() == 0; + } + + @Override + @TruffleBoundary + public MapCursor getEntries() { + return new MapCursor<>() { + int current = -1; + + @Override + public boolean advance() { + current++; + if (current >= totalEntries) { + return false; + } else { + while (EconomicMapImpl.this.getKey(current) == null) { + // Skip over null entries + current++; + } + return true; + } + } + + @SuppressWarnings("unchecked") + @Override + public K getKey() { + return (K) EconomicMapImpl.this.getKey(current); + } + + @SuppressWarnings("unchecked") + @Override + public V getValue() { + return (V) EconomicMapImpl.this.getValue(current); + } + + @Override + @TruffleBoundary + public void remove() { + if (hasHashArray()) { + EconomicMapImpl.this.findAndRemoveHash(EconomicMapImpl.this.getKey(current)); + } + current = EconomicMapImpl.this.remove(current) - 1; + } + + @SuppressWarnings("unchecked") + @Override + public V setValue(V newValue) { + V oldValue = (V) EconomicMapImpl.this.getValue(current); + EconomicMapImpl.this.setValue(current, newValue); + return oldValue; + } + }; + } + + @SuppressWarnings("unchecked") + @Override + @TruffleBoundary + public void replaceAll(BiFunction function) { + for (int i = 0; i < totalEntries; i++) { + Object entryKey = getKey(i); + if (entryKey != null) { + Object newValue = function.apply((K) entryKey, (V) getValue(i)); + setValue(i, newValue); + } + } + } + + private @Nullable Object getKey(int index) { + return entries[index << 1]; + } + + private void setKey(int index, @Nullable Object newValue) { + entries[index << 1] = newValue; + } + + private void setValue(int index, Object newValue) { + Object oldValue = getRawValue(index); + if (oldValue instanceof CollisionLink) { + CollisionLink collisionLink = (CollisionLink) oldValue; + setRawValue(index, new CollisionLink(newValue, collisionLink.next)); + } else { + setRawValue(index, newValue); + } + } + + private void setRawValue(int index, @Nullable Object newValue) { + entries[(index << 1) + 1] = newValue; + } + + private @Nullable Object getRawValue(int index) { + return entries[(index << 1) + 1]; + } + + private @Nullable Object getValue(int index) { + Object object = getRawValue(index); + if (object instanceof CollisionLink) { + return ((CollisionLink) object).value; + } + return object; + } + + private final boolean isSet; + + @Override + @TruffleBoundary + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(isSet ? "set(size=" : "map(size=").append(size()).append(", {"); + String sep = ""; + MapCursor cursor = getEntries(); + while (cursor.advance()) { + builder.append(sep); + if (isSet) { + builder.append(cursor.getKey()); + } else { + builder + .append("(") + .append(cursor.getKey()) + .append(",") + .append(cursor.getValue()) + .append(")"); + } + sep = ","; + } + builder.append("})"); + return builder.toString(); + } + + @Override + public Iterator iterator() { + return new SparseMapIterator<>() { + @SuppressWarnings("unchecked") + @Override + public K next() { + Object result; + while ((result = getKey(current++)) == null) { + // skip null entries + } + return (K) result; + } + }; + } + + @Override + public boolean contains(K element) { + return containsKey(element); + } + + @SuppressWarnings("unchecked") + @Override + public boolean add(K element) { + return put(element, (V) element) == null; + } + + @Override + public void remove(K element) { + removeKey(element); + } + + @Override + public Equivalence getEquivalenceStrategy() { + if (strategy == null) { + return Equivalence.IDENTITY; + } + return strategy; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/EconomicMapUtil.java b/pkl-core/src/main/java/org/pkl/core/collection/EconomicMapUtil.java new file mode 100644 index 000000000..dbac10d9d --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/EconomicMapUtil.java @@ -0,0 +1,151 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import java.util.Comparator; +import java.util.Objects; +import org.pkl.core.util.Nullable; + +/** + * Utility methods for the {@link EconomicMap}. + * + * @since 23.0 + */ +public final class EconomicMapUtil { + /** + * @since 23.0 + */ + private EconomicMapUtil() {} + + /** + * Compares maps for equality. The maps are equal iff they share the same {@link Equivalence + * equivalence strategy}, their keys are equal with respect to the strategy and the values are + * equal as determined by the {@link Objects#equals(Object, Object) equals} method. + * + * @param lhs the first map to be compared + * @param rhs the second map to be compared + * @return {@code true} iff the maps are equal + * @since 23.0 + */ + @TruffleBoundary + public static boolean equals( + @Nullable UnmodifiableEconomicMap lhs, @Nullable UnmodifiableEconomicMap rhs) { + if (lhs == rhs) { + return true; + } + if (lhs == null + || rhs == null + || lhs.size() != rhs.size() + || !Objects.equals(lhs.getEquivalenceStrategy(), rhs.getEquivalenceStrategy())) { + return false; + } + UnmodifiableMapCursor cursor = rhs.getEntries(); + while (cursor.advance()) { + if (!lhs.containsKey(cursor.getKey()) + || !Objects.equals(lhs.get(cursor.getKey()), cursor.getValue())) { + return false; + } + } + return true; + } + + /** + * Computes an order-independent hash code for an {@link EconomicMap}. + * + * @param map the input map or {@code null} + * @return the hash code of the map + * @since 23.0 + */ + @TruffleBoundary + public static int hashCode(@Nullable UnmodifiableEconomicMap map) { + if (map == null) { + return -1; + } + int keyHash = 0; + int valueHash = 0; + UnmodifiableMapCursor cursor = map.getEntries(); + while (cursor.advance()) { + keyHash ^= cursor.getKey().hashCode(); + if (cursor.getValue() != null) { + valueHash ^= cursor.getValue().hashCode(); + } + } + return keyHash + 31 * valueHash; + } + + /** + * Returns an {@link EconomicSet} of the keys contained in a map. + * + * @param map the input map + * @return an {@link EconomicSet} of the keys contained in a map + * @since 23.0 + */ + @TruffleBoundary + public static EconomicSet keySet(EconomicMap map) { + EconomicSet set = EconomicSet.create(map.size()); + for (K key : map.getKeys()) { + set.add(key); + } + return set; + } + + /** + * Creates a lexicographical map comparator using the provided key and value comparators. The maps + * are treated as if they were lists with the structure {@code {key1, value1, key2, value2, ...}}. + * The comparison starts by comparing their {@code key1} and if they are equal, it goes on to + * compare {@code value1}, then {@code key2}, {@code value2} and so on. If one of the maps is + * shorter, the comparators are called with {@code null} values in place of the missing + * keys/values. + * + * @param keyComparator a comparator to compare keys + * @param valueComparator a comparator to compare values + * @return a lexicographical map comparator + * @since 23.0 + */ + @TruffleBoundary + public static Comparator> lexicographicalComparator( + Comparator keyComparator, Comparator valueComparator) { + return new Comparator<>() { + @Override + public int compare(UnmodifiableEconomicMap map1, UnmodifiableEconomicMap map2) { + if (map2.size() > map1.size()) { + return -compare(map2, map1); + } + assert map1.size() >= map2.size(); + UnmodifiableMapCursor cursor1 = map1.getEntries(); + UnmodifiableMapCursor cursor2 = map2.getEntries(); + while (cursor1.advance()) { + K key2 = null; + V value2 = null; + if (cursor2.advance()) { + key2 = cursor2.getKey(); + value2 = cursor2.getValue(); + } + int order = keyComparator.compare(cursor1.getKey(), key2); + if (order != 0) { + return order; + } + order = valueComparator.compare(cursor1.getValue(), value2); + if (order != 0) { + return order; + } + } + return 0; + } + }; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/EconomicMapWrap.java b/pkl-core/src/main/java/org/pkl/core/collection/EconomicMapWrap.java new file mode 100644 index 000000000..1023e444c --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/EconomicMapWrap.java @@ -0,0 +1,173 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import java.util.Iterator; +import java.util.Map; +import java.util.function.BiFunction; +import org.pkl.core.util.Nullable; + +/** + * Wraps an existing {@link Map} as an {@link EconomicMap}. + * + * @since 21.1 + */ +public class EconomicMapWrap implements EconomicMap { + + private final Map map; + + /** + * @since 21.1 + */ + public EconomicMapWrap(Map map) { + this.map = map; + } + + /** + * @since 21.1 + */ + @Override + public V get(K key) { + V result = map.get(key); + return result; + } + + /** + * @since 21.1 + */ + @Override + public V put(K key, V value) { + V result = map.put(key, value); + return result; + } + + /** + * @since 21.1 + */ + @Override + public @Nullable V putIfAbsent(K key, @Nullable V value) { + V result = map.putIfAbsent(key, value); + return result; + } + + /** + * @since 21.1 + */ + @Override + public int size() { + int result = map.size(); + return result; + } + + /** + * @since 21.1 + */ + @Override + public boolean containsKey(K key) { + return map.containsKey(key); + } + + /** + * @since 21.1 + */ + @Override + public void clear() { + map.clear(); + } + + /** + * @since 21.1 + */ + @Override + public V removeKey(K key) { + V result = map.remove(key); + return result; + } + + /** + * @since 21.1 + */ + @Override + public Iterable getValues() { + return map.values(); + } + + /** + * @since 21.1 + */ + @Override + public Iterable getKeys() { + return map.keySet(); + } + + /** + * @since 21.1 + */ + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * @since 21.1 + */ + @Override + public MapCursor getEntries() { + Iterator> iterator = map.entrySet().iterator(); + return new MapCursor<>() { + + private Map.Entry current; + + @Override + public boolean advance() { + boolean result = iterator.hasNext(); + if (result) { + current = iterator.next(); + } + + return result; + } + + @Override + public K getKey() { + return current.getKey(); + } + + @Override + public V getValue() { + return current.getValue(); + } + + @Override + public void remove() { + iterator.remove(); + } + + @Override + public V setValue(V newValue) { + return current.setValue(newValue); + } + }; + } + + /** + * @since 21.1 + */ + @Override + public void replaceAll(BiFunction function) { + map.replaceAll(function); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/EconomicSet.java b/pkl-core/src/main/java/org/pkl/core/collection/EconomicSet.java new file mode 100644 index 000000000..c213d94e4 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/EconomicSet.java @@ -0,0 +1,196 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import java.util.Iterator; + +/** + * Memory efficient set data structure. + * + * @since 19.0 + */ +public interface EconomicSet extends UnmodifiableEconomicSet { + + /** + * Adds {@code element} to this set if it is not already present. + * + * @return {@code true} if this set did not already contain {@code element}. + * @since 19.0 + */ + boolean add(E element); + + /** + * Removes {@code element} from this set if it is present. This set will not contain {@code + * element} once the call returns. + * + * @since 19.0 + */ + void remove(E element); + + /** + * Removes all of the elements from this set. The set will be empty after this call returns. + * + * @since 19.0 + */ + void clear(); + + /** + * Adds all of the elements in {@code other} to this set if they're not already present. + * + * @since 19.0 + */ + default void addAll(EconomicSet other) { + addAll(other.iterator()); + } + + /** + * Adds all of the elements in {@code values} to this set if they're not already present. + * + * @since 19.0 + */ + @TruffleBoundary + default void addAll(Iterable values) { + addAll(values.iterator()); + } + + /** + * Adds all of the elements enumerated by {@code iterator} to this set if they're not already + * present. + * + * @since 19.0 + */ + @TruffleBoundary + default void addAll(Iterator iterator) { + while (iterator.hasNext()) { + add(iterator.next()); + } + } + + /** + * Removes from this set all of its elements that are contained in {@code other}. + * + * @since 19.0 + */ + default void removeAll(EconomicSet other) { + removeAll(other.iterator()); + } + + /** + * Removes from this set all of its elements that are contained in {@code values}. + * + * @since 19.0 + */ + @TruffleBoundary + default void removeAll(Iterable values) { + removeAll(values.iterator()); + } + + /** + * Removes from this set all of its elements that are enumerated by {@code iterator}. + * + * @since 19.0 + */ + @TruffleBoundary + default void removeAll(Iterator iterator) { + while (iterator.hasNext()) { + remove(iterator.next()); + } + } + + /** + * Removes from this set all of its elements that are not contained in {@code other}. + * + * @since 19.0 + */ + @TruffleBoundary + default void retainAll(EconomicSet other) { + Iterator iterator = iterator(); + while (iterator.hasNext()) { + E key = iterator.next(); + if (!other.contains(key)) { + iterator.remove(); + } + } + } + + /** + * Creates a new set guaranteeing insertion order when iterating over its elements with the + * default {@link Equivalence#DEFAULT} comparison strategy. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicSet create() { + return EconomicSet.create(Equivalence.DEFAULT); + } + + /** + * Creates a new set guaranteeing insertion order when iterating over its elements. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicSet create(Equivalence strategy) { + return EconomicMapImpl.create(strategy, true); + } + + /** + * Creates a new set guaranteeing insertion order when iterating over its elements with the + * default {@link Equivalence#DEFAULT} comparison strategy and inserts all elements of the + * specified collection. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicSet create(int initialCapacity) { + return EconomicSet.create(Equivalence.DEFAULT, initialCapacity); + } + + /** + * Creates a new set guaranteeing insertion order when iterating over its elements with the + * default {@link Equivalence#DEFAULT} comparison strategy and inserts all elements of the + * specified collection. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicSet create(UnmodifiableEconomicSet c) { + return EconomicSet.create(Equivalence.DEFAULT, c); + } + + /** + * Creates a new set guaranteeing insertion order when iterating over its elements and initializes + * with the given capacity. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicSet create(Equivalence strategy, int initialCapacity) { + return EconomicMapImpl.create(strategy, initialCapacity, true); + } + + /** + * Creates a new set guaranteeing insertion order when iterating over its elements and inserts all + * elements of the specified collection. + * + * @since 19.0 + */ + @TruffleBoundary + static EconomicSet create(Equivalence strategy, UnmodifiableEconomicSet c) { + return EconomicMapImpl.create(strategy, c, true); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/EmptyMap.java b/pkl-core/src/main/java/org/pkl/core/collection/EmptyMap.java new file mode 100644 index 000000000..35c55d5d4 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/EmptyMap.java @@ -0,0 +1,131 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.function.BiFunction; + +/** Singleton instances for empty maps and the corresponding iterators and cursors. */ +class EmptyMap { + + static final MapCursor EMPTY_CURSOR = + new MapCursor<>() { + @Override + public void remove() { + throw new NoSuchElementException("Empty cursor does not have elements"); + } + + @Override + public boolean advance() { + return false; + } + + @Override + public Object getKey() { + throw new NoSuchElementException("Empty cursor does not have elements"); + } + + @Override + public Object getValue() { + throw new NoSuchElementException("Empty cursor does not have elements"); + } + + @Override + public Object setValue(Object newValue) { + throw new NoSuchElementException("Empty cursor does not have elements"); + } + }; + + static final Iterator EMPTY_ITERATOR = + new Iterator<>() { + @Override + public boolean hasNext() { + return false; + } + + @Override + public Object next() { + throw new NoSuchElementException("Empty iterator does not have elements"); + } + }; + + static final Iterable EMPTY_ITERABLE = + new Iterable<>() { + @Override + public Iterator iterator() { + return EMPTY_ITERATOR; + } + }; + + static final EconomicMap EMPTY_MAP = + new EconomicMap<>() { + @Override + public Object put(Object key, Object value) { + throw new IllegalArgumentException("Cannot modify the always-empty map"); + } + + @Override + public void clear() { + throw new IllegalArgumentException("Cannot modify the always-empty map"); + } + + @Override + public Object removeKey(Object key) { + throw new IllegalArgumentException("Cannot modify the always-empty map"); + } + + @Override + public Object get(Object key) { + return null; + } + + @Override + public boolean containsKey(Object key) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public Iterable getValues() { + return EMPTY_ITERABLE; + } + + @Override + public Iterable getKeys() { + return EMPTY_ITERABLE; + } + + @Override + public MapCursor getEntries() { + return EMPTY_CURSOR; + } + + @Override + public void replaceAll(BiFunction function) { + throw new IllegalArgumentException("Cannot modify the always-empty map"); + } + }; +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/Equivalence.java b/pkl-core/src/main/java/org/pkl/core/collection/Equivalence.java new file mode 100644 index 000000000..40f7fde7d --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/Equivalence.java @@ -0,0 +1,114 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; + +/** + * Strategy for comparing two objects. Default predefined strategies are {@link #DEFAULT}, {@link + * #IDENTITY}, and {@link #IDENTITY_WITH_SYSTEM_HASHCODE}. + * + * @since 19.0 + */ +public abstract class Equivalence { + + /** + * Default equivalence calling {@link #equals(Object)} to check equality and {@link #hashCode()} + * for obtaining hash values. Do not change the logic of this class as it may be inlined in other + * places. + * + * @since 19.0 + */ + public static final Equivalence DEFAULT = + new Equivalence() { + + @Override + @TruffleBoundary + public boolean equals(Object a, Object b) { + return a.equals(b); + } + + @Override + @TruffleBoundary + public int hashCode(Object o) { + return o.hashCode(); + } + }; + + /** + * Identity equivalence using {@code ==} to check equality and {@link #hashCode()} for obtaining + * hash values. Do not change the logic of this class as it may be inlined in other places. + * + * @since 19.0 + */ + public static final Equivalence IDENTITY = + new Equivalence() { + + @Override + public boolean equals(Object a, Object b) { + return a == b; + } + + @Override + @TruffleBoundary + public int hashCode(Object o) { + return o.hashCode(); + } + }; + + /** + * Identity equivalence using {@code ==} to check equality and {@link + * System#identityHashCode(Object)} for obtaining hash values. Do not change the logic of this + * class as it may be inlined in other places. + * + * @since 19.0 + */ + public static final Equivalence IDENTITY_WITH_SYSTEM_HASHCODE = + new Equivalence() { + + @Override + public boolean equals(Object a, Object b) { + return a == b; + } + + @Override + public int hashCode(Object o) { + return System.identityHashCode(o); + } + }; + + /** + * Subclass for creating custom equivalence definitions. + * + * @since 19.0 + */ + protected Equivalence() {} + + /** + * Returns {@code true} if the non-{@code null} arguments are equal to each other and {@code + * false} otherwise. + * + * @since 19.0 + */ + public abstract boolean equals(Object a, Object b); + + /** + * Returns the hash code of a non-{@code null} argument {@code o}. + * + * @since 19.0 + */ + public abstract int hashCode(Object o); +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/MapCursor.java b/pkl-core/src/main/java/org/pkl/core/collection/MapCursor.java new file mode 100644 index 000000000..41bdce00f --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/MapCursor.java @@ -0,0 +1,47 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; + +/** + * Cursor to iterate over a mutable map. + * + * @since 19.0 + */ +public interface MapCursor extends UnmodifiableMapCursor { + /** + * Remove the current entry from the map. May only be called once. After calling {@link + * #remove()}, it is no longer valid to call {@link #getKey()} or {@link #getValue()} on the + * current entry. + * + * @since 19.0 + */ + @TruffleBoundary + void remove(); + + /** + * Set the value of the current entry. + * + * @param newValue new value to be associated with the current key. + * @return previous value associated with the current key. + * @since 22.1 + */ + @TruffleBoundary + default V setValue(V newValue) { + throw new UnsupportedOperationException(); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/Pair.java b/pkl-core/src/main/java/org/pkl/core/collection/Pair.java new file mode 100644 index 000000000..a4fc0d9f3 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/Pair.java @@ -0,0 +1,155 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import java.util.Objects; +import org.pkl.core.util.Nullable; + +/** + * Utility class representing a pair of values. + * + * @since 19.0 + */ +public final class Pair { + + private static final Pair EMPTY = new Pair<>(null, null); + + private final @Nullable L left; + private final @Nullable R right; + + /** + * Returns an empty pair. + * + * @since 19.0 + */ + @SuppressWarnings("unchecked") + public static Pair empty() { + return (Pair) EMPTY; + } + + /** + * Constructs a pair with its left value being {@code left}, or returns an empty pair if {@code + * left} is null. + * + * @return the constructed pair or an empty pair if {@code left} is null. + * @since 19.0 + */ + public static Pair createLeft(@Nullable L left) { + if (left == null) { + return empty(); + } else { + return new Pair<>(left, null); + } + } + + /** + * Constructs a pair with its right value being {@code right}, or returns an empty pair if {@code + * right} is null. + * + * @return the constructed pair or an empty pair if {@code right} is null. + * @since 19.0 + */ + public static Pair createRight(@Nullable R right) { + if (right == null) { + return empty(); + } else { + return new Pair<>(null, right); + } + } + + /** + * Constructs a pair with its left value being {@code left}, and its right value being {@code + * right}, or returns an empty pair if both inputs are null. + * + * @return the constructed pair or an empty pair if both inputs are null. + * @since 19.0 + */ + public static Pair create(@Nullable L left, @Nullable R right) { + if (right == null && left == null) { + return empty(); + } else { + return new Pair<>(left, right); + } + } + + private Pair(@Nullable L left, @Nullable R right) { + this.left = left; + this.right = right; + } + + /** + * Returns the left value of this pair. + * + * @since 19.0 + */ + public @Nullable L getLeft() { + return left; + } + + /** + * Returns the right value of this pair. + * + * @since 19.0 + */ + public @Nullable R getRight() { + return right; + } + + /** + * {@inheritDoc} + * + * @since 19.0 + */ + @Override + @TruffleBoundary + public int hashCode() { + return Objects.hashCode(left) + 31 * Objects.hashCode(right); + } + + /** + * {@inheritDoc} + * + * @since 19.0 + */ + @SuppressWarnings("unchecked") + @Override + @TruffleBoundary + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof Pair) { + Pair pair = (Pair) obj; + return Objects.equals(left, pair.left) && Objects.equals(right, pair.right); + } + + return false; + } + + /** + * {@inheritDoc} + * + * @since 19.0 + */ + @Override + @TruffleBoundary + public String toString() { + // String.format isn't used here since it tends to pull a lot of types into image. + return "(" + left + ", " + right + ")"; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableEconomicMap.java b/pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableEconomicMap.java new file mode 100644 index 000000000..a162521e5 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableEconomicMap.java @@ -0,0 +1,106 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import org.pkl.core.util.Nullable; + +/** + * Unmodifiable memory efficient map. See {@link EconomicMap} for the underlying data structure and + * its properties. + * + * @since 19.0 + */ +public interface UnmodifiableEconomicMap { + + /** + * Returns the value to which {@code key} is mapped, or {@code null} if this map contains no + * mapping for {@code key}. The {@code key} must not be {@code null}. + * + * @since 19.0 + */ + @TruffleBoundary + @Nullable + V get(K key); + + /** + * Returns the value to which {@code key} is mapped, or {@code defaultValue} if this map contains + * no mapping for {@code key}. The {@code key} must not be {@code null}. + * + * @since 19.0 + */ + @TruffleBoundary + default @Nullable V get(K key, V defaultValue) { + V v = get(key); + if (v == null) { + return defaultValue; + } + return v; + } + + /** + * Returns {@code true} if this map contains a mapping for {@code key}. Always returns {@code + * false} if the {@code key} is {@code null}. + * + * @since 19.0 + */ + @TruffleBoundary + boolean containsKey(K key); + + /** + * Returns the number of key-value mappings in this map. + * + * @since 19.0 + */ + int size(); + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + * @since 19.0 + */ + boolean isEmpty(); + + /** + * Returns a {@link Iterable} view of the values contained in this map. + * + * @since 19.0 + */ + Iterable getValues(); + + /** + * Returns a {@link Iterable} view of the keys contained in this map. + * + * @since 19.0 + */ + Iterable getKeys(); + + /** + * Returns a {@link UnmodifiableMapCursor} view of the mappings contained in this map. + * + * @since 19.0 + */ + UnmodifiableMapCursor getEntries(); + + /** + * Returns the strategy used to compare keys. + * + * @since 23.0 + */ + default Equivalence getEquivalenceStrategy() { + return Equivalence.DEFAULT; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableEconomicSet.java b/pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableEconomicSet.java new file mode 100644 index 000000000..4ae1f6049 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableEconomicSet.java @@ -0,0 +1,69 @@ +/* + * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.core.collection; + +/** + * Unmodifiable memory efficient set data structure. + * + * @since 19.0 + */ +public interface UnmodifiableEconomicSet extends Iterable { + + /** + * Returns {@code true} if this set contains a mapping for the {@code element}. + * + * @since 19.0 + */ + boolean contains(E element); + + /** + * Returns the number of elements in this set. + * + * @since 19.0 + */ + int size(); + + /** + * Returns {@code true} if this set contains no elements. + * + * @since 19.0 + */ + boolean isEmpty(); + + /** + * Stores all of the elements in this set into {@code target}. An {@link + * UnsupportedOperationException} will be thrown if the length of {@code target} does not match + * the size of this set. + * + * @return an array containing all the elements in this set. + * @throws UnsupportedOperationException if the length of {@code target} does not equal the size + * of this set. + * @since 19.0 + */ + default E[] toArray(E[] target) { + if (target.length != size()) { + throw new UnsupportedOperationException( + "Length of target array must equal the size of the set."); + } + + int index = 0; + for (E element : this) { + target[index++] = element; + } + + return target; + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/util/EconomicSets.java b/pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableMapCursor.java similarity index 54% rename from pkl-core/src/main/java/org/pkl/core/util/EconomicSets.java rename to pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableMapCursor.java index 23d2ec0b6..d21a72801 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/EconomicSets.java +++ b/pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableMapCursor.java @@ -13,26 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pkl.core.util; +package org.pkl.core.collection; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import org.graalvm.collections.EconomicSet; - -public final class EconomicSets { - private EconomicSets() {} - - @TruffleBoundary - public static EconomicSet create() { - return EconomicSet.create(); - } +/** + * Cursor to iterate over a map without changing its contents. + * + * @since 19.0 + */ +public interface UnmodifiableMapCursor { + /** + * Advances to the next entry. + * + * @return {@code true} if a next entry exists, {@code false} if there is no next entry. + * @since 19.0 + */ + boolean advance(); - @TruffleBoundary - public static boolean add(EconomicSet self, E element) { - return self.add(element); - } + /** + * The key of the current entry. + * + * @since 19.0 + */ + K getKey(); - @TruffleBoundary - public static boolean contains(EconomicSet self, E element) { - return self.contains(element); - } + /** + * The value of the current entry. + * + * @since 19.0 + */ + V getValue(); } diff --git a/pkl-core/src/main/java/org/pkl/core/collection/package-info.java b/pkl-core/src/main/java/org/pkl/core/collection/package-info.java new file mode 100644 index 000000000..a0a1da336 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/collection/package-info.java @@ -0,0 +1,50 @@ +/* + * This package contains source code from: + * + * https://github.com/oracle/graal + * + * Original license: + * + * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +@NonnullByDefault +package org.pkl.core.collection; + +import org.pkl.core.util.NonnullByDefault; diff --git a/pkl-core/src/main/java/org/pkl/core/module/PathElement.java b/pkl-core/src/main/java/org/pkl/core/module/PathElement.java index bd8421ab2..182f025ad 100644 --- a/pkl-core/src/main/java/org/pkl/core/module/PathElement.java +++ b/pkl-core/src/main/java/org/pkl/core/module/PathElement.java @@ -20,8 +20,7 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; -import org.graalvm.collections.EconomicMap; -import org.pkl.core.util.EconomicMaps; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.util.Nullable; public class PathElement { @@ -81,7 +80,7 @@ public String toString() { } public static final class TreePathElement extends PathElement { - private final EconomicMap children = EconomicMaps.create(); + private final EconomicMap children = EconomicMap.create(); public TreePathElement(String name, boolean isDirectory) { super(name, isDirectory); @@ -117,7 +116,7 @@ public EconomicMap getChildren() { public List getChildrenValues() { var ret = new ArrayList(children.size()); - for (var elem : EconomicMaps.getValues(children)) { + for (var elem : children.getValues()) { ret.add(elem); } return ret; diff --git a/pkl-core/src/main/java/org/pkl/core/module/ProjectDependenciesManager.java b/pkl-core/src/main/java/org/pkl/core/module/ProjectDependenciesManager.java index 1194f9386..2059c398a 100644 --- a/pkl-core/src/main/java/org/pkl/core/module/ProjectDependenciesManager.java +++ b/pkl-core/src/main/java/org/pkl/core/module/ProjectDependenciesManager.java @@ -21,10 +21,10 @@ import java.util.Map; import java.util.Objects; import javax.annotation.concurrent.GuardedBy; -import org.graalvm.collections.EconomicMap; import org.pkl.core.PklBugException; import org.pkl.core.SecurityManager; import org.pkl.core.SecurityManagerException; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.packages.Dependency; import org.pkl.core.packages.DependencyMetadata; import org.pkl.core.packages.PackageLoadError; @@ -34,7 +34,6 @@ import org.pkl.core.project.ProjectDeps; import org.pkl.core.runtime.ModuleResolver; import org.pkl.core.runtime.VmExceptionBuilder; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.IoUtils; import org.pkl.core.util.json.Json.JsonParseException; @@ -56,11 +55,11 @@ public final class ProjectDependenciesManager { @GuardedBy("lock") private final EconomicMap> localPackageDependencies = - EconomicMaps.create(); + EconomicMap.create(); @GuardedBy("lock") private final EconomicMap> packageDependencies = - EconomicMaps.create(); + EconomicMap.create(); private final Object lock = new Object(); diff --git a/pkl-core/src/main/java/org/pkl/core/packages/PackageResolvers.java b/pkl-core/src/main/java/org/pkl/core/packages/PackageResolvers.java index 7da23f450..c7ad19d4b 100644 --- a/pkl-core/src/main/java/org/pkl/core/packages/PackageResolvers.java +++ b/pkl-core/src/main/java/org/pkl/core/packages/PackageResolvers.java @@ -43,9 +43,9 @@ import java.util.stream.StreamSupport; import java.util.zip.ZipInputStream; import javax.annotation.concurrent.GuardedBy; -import org.graalvm.collections.EconomicMap; import org.pkl.core.SecurityManager; import org.pkl.core.SecurityManagerException; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.http.HttpClient; import org.pkl.core.module.FileResolver; import org.pkl.core.module.PathElement; @@ -53,7 +53,6 @@ import org.pkl.core.runtime.FileSystemManager; import org.pkl.core.runtime.VmExceptionBuilder; import org.pkl.core.util.ByteArrayUtils; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.HttpUtils; import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; @@ -78,7 +77,7 @@ abstract static class AbstractPackageResolver implements PackageResolver { protected AbstractPackageResolver(SecurityManager securityManager, HttpClient httpClient) { this.securityManager = securityManager; this.httpClient = httpClient; - cachedDependencyMetadata = EconomicMaps.create(); + cachedDependencyMetadata = EconomicMap.create(); } /** Retrieves a dependency's metadata file. */ @@ -250,11 +249,11 @@ private void checkNotClosed() { static final class InMemoryPackageResolver extends AbstractPackageResolver { @GuardedBy("lock") private final EconomicMap> cachedEntries = - EconomicMaps.create(); + EconomicMap.create(); @GuardedBy("lock") private final EconomicMap cachedTreePathElementRoots = - EconomicMaps.create(); + EconomicMap.create(); InMemoryPackageResolver(SecurityManager securityManager, HttpClient httpClient) { super(securityManager, httpClient); @@ -278,7 +277,7 @@ private void ensurePackageDownloaded(PackageUri uri, @Nullable Checksums checksu return; } var metadata = getDependencyMetadata(uri, checksums); - var cachedEntrySet = EconomicMaps.create(); + var cachedEntrySet = EconomicMap.create(); var packageBytes = getPackageBytes(uri, metadata); try (var zipInputStream = new ZipInputStream(new ByteArrayInputStream(packageBytes))) { var rootPathElement = new TreePathElement("", true); @@ -415,7 +414,7 @@ static final class DiskCachedPackageResolver extends AbstractPackageResolver { private static final String CACHE_DIR_PREFIX = "package-2"; @GuardedBy("lock") - private final EconomicMap fileSystems = EconomicMaps.create(); + private final EconomicMap fileSystems = EconomicMap.create(); private static final Set FILE_PERMISSIONS = EnumSet.of( diff --git a/pkl-core/src/main/java/org/pkl/core/project/ProjectDependenciesResolver.java b/pkl-core/src/main/java/org/pkl/core/project/ProjectDependenciesResolver.java index ba6b7b11d..33f57acd9 100644 --- a/pkl-core/src/main/java/org/pkl/core/project/ProjectDependenciesResolver.java +++ b/pkl-core/src/main/java/org/pkl/core/project/ProjectDependenciesResolver.java @@ -19,10 +19,10 @@ import java.io.UncheckedIOException; import java.io.Writer; import java.nio.file.Path; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; import org.pkl.core.PklException; import org.pkl.core.SecurityManagerException; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.EconomicSet; import org.pkl.core.packages.Checksums; import org.pkl.core.packages.Dependency; import org.pkl.core.packages.Dependency.LocalDependency; @@ -30,8 +30,6 @@ import org.pkl.core.packages.PackageLoadError; import org.pkl.core.packages.PackageResolver; import org.pkl.core.packages.PackageUri; -import org.pkl.core.util.EconomicMaps; -import org.pkl.core.util.EconomicSets; import org.pkl.core.util.ErrorMessages; import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; @@ -49,9 +47,9 @@ public final class ProjectDependenciesResolver { private final PackageResolver packageResolver; private final Writer logWriter; private final EconomicMap resolvedDependencies = - EconomicMaps.create(); + EconomicMap.create(); - private final EconomicSet alreadyHandledDependencies = EconomicSets.create(); + private final EconomicSet alreadyHandledDependencies = EconomicSet.create(); public ProjectDependenciesResolver( Project project, PackageResolver packageResolver, Writer logWriter) { @@ -116,7 +114,7 @@ private void resolveDependenciesOfPackageUri( } var dependencyWithChecksum = new RemoteDependency(packageUri, computedChecksums); updateDependency(dependencyWithChecksum); - EconomicSets.add(alreadyHandledDependencies, packageUri); + alreadyHandledDependencies.add(packageUri); for (var transitiveDependency : metadata.getDependencies().values()) { resolveDependenciesOfPackageUri( transitiveDependency.getPackageUri().toProjectPackageUri(), @@ -142,7 +140,7 @@ private void updateDependency(Dependency dependency) { var currentDependency = resolvedDependencies.get(canonicalPackageUri); if (currentDependency == null || currentDependency.getVersion().compareTo(dependency.getVersion()) < 0) { - EconomicMaps.put(resolvedDependencies, canonicalPackageUri, dependency); + resolvedDependencies.put(canonicalPackageUri, dependency); } } } diff --git a/pkl-core/src/main/java/org/pkl/core/project/ProjectDeps.java b/pkl-core/src/main/java/org/pkl/core/project/ProjectDeps.java index bf893efd2..773eecff6 100644 --- a/pkl-core/src/main/java/org/pkl/core/project/ProjectDeps.java +++ b/pkl-core/src/main/java/org/pkl/core/project/ProjectDeps.java @@ -23,9 +23,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Map.Entry; -import java.util.Objects; import java.util.Set; -import org.graalvm.collections.EconomicMap; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.EconomicMapUtil; import org.pkl.core.packages.Checksums; import org.pkl.core.packages.Dependency; import org.pkl.core.packages.Dependency.LocalDependency; @@ -34,7 +34,6 @@ import org.pkl.core.packages.PackageLoadError; import org.pkl.core.packages.PackageUtils; import org.pkl.core.runtime.VmExceptionBuilder; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; import org.pkl.core.util.json.Json; @@ -103,7 +102,7 @@ private static EconomicMap parseResolvedDepende if (!(object instanceof JsObject jsObj)) { throw new FormatException("resolvedDependencies", "object", object.getClass()); } - var ret = EconomicMaps.create(jsObj.size()); + var ret = EconomicMap.create(jsObj.size()); for (var entry : jsObj.entrySet()) { Dependency resolvedDependency = parseResolvedDependency(entry); var canonicalPackageUri = CanonicalPackageUri.of(entry.getKey()); @@ -154,16 +153,15 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof ProjectDeps that)) { return false; } - ProjectDeps that = (ProjectDeps) o; - return EconomicMaps.equals(resolvedDependencies, that.resolvedDependencies); + return EconomicMapUtil.equals(resolvedDependencies, that.resolvedDependencies); } @Override public int hashCode() { - return Objects.hash(resolvedDependencies); + return EconomicMapUtil.hashCode(resolvedDependencies); } private static final class ProjectDepsWriter { diff --git a/pkl-core/src/main/java/org/pkl/core/project/ProjectPackager.java b/pkl-core/src/main/java/org/pkl/core/project/ProjectPackager.java index ef7fe04ef..02c35293a 100644 --- a/pkl-core/src/main/java/org/pkl/core/project/ProjectPackager.java +++ b/pkl-core/src/main/java/org/pkl/core/project/ProjectPackager.java @@ -36,13 +36,13 @@ import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.graalvm.collections.EconomicMap; import org.pkl.core.PklBugException; import org.pkl.core.PklException; import org.pkl.core.SecurityManager; import org.pkl.core.SecurityManagerException; import org.pkl.core.StackFrameTransformer; import org.pkl.core.ast.builder.ImportsAndReadsParser; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.http.HttpClient; import org.pkl.core.module.ModuleKeyFactories; import org.pkl.core.module.ModuleKeys; diff --git a/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java b/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java index 3b437a297..1f82186da 100644 --- a/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java +++ b/pkl-core/src/main/java/org/pkl/core/repl/ReplServer.java @@ -24,7 +24,6 @@ import java.util.*; import java.util.stream.Collectors; import org.antlr.v4.runtime.tree.TerminalNode; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.graalvm.polyglot.Context; import org.pkl.core.*; import org.pkl.core.SecurityManager; @@ -33,6 +32,8 @@ import org.pkl.core.ast.member.*; import org.pkl.core.ast.repl.ResolveClassMemberNode; import org.pkl.core.ast.type.TypeNode; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.http.HttpClient; import org.pkl.core.module.*; import org.pkl.core.packages.PackageResolver; @@ -49,7 +50,6 @@ import org.pkl.core.repl.ReplResponse.InvalidRequest; import org.pkl.core.resource.ResourceReader; import org.pkl.core.runtime.*; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.IoUtils; import org.pkl.core.util.MutableReference; import org.pkl.core.util.Nullable; @@ -393,7 +393,7 @@ private List handleReset() { } private VmTyped createEmptyReplModule(@Nullable VmTyped parent) { - return createReplModule(List.of(), List.of(), EconomicMaps.create(), parent); + return createReplModule(List.of(), List.of(), EconomicMap.create(), parent); } private VmTyped createReplModule( diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/FileSystemManager.java b/pkl-core/src/main/java/org/pkl/core/runtime/FileSystemManager.java index a824b5acf..d536fc11d 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/FileSystemManager.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/FileSystemManager.java @@ -32,8 +32,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.graalvm.collections.EconomicMap; -import org.pkl.core.util.EconomicMaps; +import org.pkl.core.collection.EconomicMap; /** * Manages file systems, potentially across multiple evaluator instances. @@ -43,7 +42,7 @@ public final class FileSystemManager { private FileSystemManager() {} - private static final EconomicMap fileSystems = EconomicMaps.create(); + private static final EconomicMap fileSystems = EconomicMap.create(); private static final Map counts = new IdentityHashMap<>(); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/MemberLookupSuggestions.java b/pkl-core/src/main/java/org/pkl/core/runtime/MemberLookupSuggestions.java index 4a18200d9..e6d0c35a0 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/MemberLookupSuggestions.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/MemberLookupSuggestions.java @@ -20,7 +20,6 @@ import org.pkl.core.ast.member.ClassMethod; import org.pkl.core.ast.member.Member; import org.pkl.core.runtime.MemberLookupSuggestions.Candidate.Kind; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; import org.pkl.core.util.StringSimilarity; @@ -74,7 +73,7 @@ public List find(boolean isImplicitReceiver) { private void addPropertyCandidates(VmObjectLike object, boolean includeLocal) { if (!memberKinds.contains(Kind.PROPERTY)) return; - for (var member : EconomicMaps.getValues(object.getMembers())) { + for (var member : object.getMembers().getValues()) { addIfSimilar(member, Candidate.Kind.PROPERTY, -1, includeLocal); } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java index efa7b9b78..c3a59391b 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/ModuleInfo.java @@ -25,7 +25,6 @@ import org.pkl.core.ast.expression.unary.AbstractImportNode; import org.pkl.core.module.ModuleKey; import org.pkl.core.module.ResolvedModuleKey; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.LateInit; import org.pkl.core.util.Nullable; @@ -127,7 +126,7 @@ public ModuleSchema getModuleSchema(VmTyped module) { var classes = new LinkedHashMap(); var typeAliases = new LinkedHashMap(); - for (var propertyDef : EconomicMaps.getValues(module.getMembers())) { + for (var propertyDef : module.getMembers().getValues()) { if (propertyDef.isImport()) { MemberNode memberNode = propertyDef.getMemberNode(); assert memberNode != null; // import is never a constant diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java b/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java index 5364b77c9..1f8357b31 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/TestRunner.java @@ -31,12 +31,12 @@ import org.pkl.core.TestResults.TestSectionName; import org.pkl.core.TestResults.TestSectionResults; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.module.ModuleKeys; import org.pkl.core.stdlib.PklConverter; import org.pkl.core.stdlib.base.PcfRenderer; import org.pkl.core.util.AnsiStringBuilder; import org.pkl.core.util.AnsiTheme; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.MutableBoolean; import org.pkl.core.util.MutableReference; @@ -340,7 +340,7 @@ private void writeExampleOutputs(Path outputFile, VmMapping examples) { new VmDynamic( VmUtils.createEmptyMaterializedFrame(), BaseModule.getDynamicClass().getPrototype(), - EconomicMaps.of( + EconomicMap.of( Identifier.EXAMPLES, VmUtils.createSyntheticObjectProperty(Identifier.EXAMPLES, "examples", examples)), 0); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java index 36725ea85..48b248d08 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmClass.java @@ -23,7 +23,6 @@ import java.util.*; import java.util.function.*; import javax.annotation.concurrent.GuardedBy; -import org.graalvm.collections.*; import org.pkl.core.Member.SourceLocation; import org.pkl.core.PClass; import org.pkl.core.PClassInfo; @@ -32,8 +31,8 @@ import org.pkl.core.ast.*; import org.pkl.core.ast.member.*; import org.pkl.core.ast.type.TypeNode; +import org.pkl.core.collection.*; import org.pkl.core.util.CollectionUtils; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.LateInit; import org.pkl.core.util.Nullable; @@ -53,8 +52,8 @@ public final class VmClass extends VmValue { private final List typeParameters; private final VmTyped prototype; - private final EconomicMap declaredProperties = EconomicMaps.create(); - private final EconomicMap declaredMethods = EconomicMaps.create(); + private final EconomicMap declaredProperties = EconomicMap.create(); + private final EconomicMap declaredMethods = EconomicMap.create(); // initialized to non-null value by `initSupertype()` for all classes but `pkl.base#Any` @CompilationFinal private @Nullable TypeNode supertypeNode; @@ -157,7 +156,7 @@ public void initSupertype(TypeNode supertypeNode, VmClass superclass) { @TruffleBoundary public void addProperty(ClassProperty property) { prototype.addProperty(property.getInitializer()); - EconomicMaps.put(declaredProperties, property.getName(), property); + declaredProperties.put(property.getName(), property); if (!property.isLocal()) { __allProperties = null; @@ -174,7 +173,7 @@ public void addProperties(Iterable properties) { @TruffleBoundary public void addMethod(ClassMethod method) { - EconomicMaps.put(declaredMethods, method.getName(), method); + declaredMethods.put(method.getName(), method); if (!method.isLocal()) { __allMethods = null; @@ -202,12 +201,12 @@ public int getTypeParameterCount() { * property was found. Does return local properties. */ public @Nullable ClassProperty getDeclaredProperty(Identifier name) { - return EconomicMaps.get(declaredProperties, name); + return declaredProperties.get(name); } /** Returns all properties declared in this class. Does include local properties. */ public Iterable getDeclaredProperties() { - return EconomicMaps.getValues(declaredProperties); + return declaredProperties.getValues(); } @Override @@ -279,12 +278,12 @@ public boolean isHiddenProperty(Object key) { * null} if no such property was found. Does not return local properties. */ public @Nullable ClassProperty getProperty(Identifier name) { - return EconomicMaps.get(getAllProperties(), name); + return getAllProperties().get(name); } /** Shorthand for {@code getProperty(name) != null}. */ public boolean hasProperty(Identifier name) { - return !isInitialized || EconomicMaps.containsKey(getAllProperties(), name); + return !isInitialized || getAllProperties().containsKey(name); } /** @@ -312,27 +311,27 @@ public UnmodifiableEconomicSet getAllHiddenPropertyNames() { /** Includes local methods. */ public boolean hasDeclaredMethod(Identifier name) { - return EconomicMaps.containsKey(declaredMethods, name); + return declaredMethods.containsKey(name); } /** Does return local methods. */ public @Nullable ClassMethod getDeclaredMethod(Identifier name) { - return EconomicMaps.get(declaredMethods, name); + return declaredMethods.get(name); } /** Includes local methods. */ public Iterable getDeclaredMethods() { - return EconomicMaps.getValues(declaredMethods); + return declaredMethods.getValues(); } /** Does not return local methods. */ public @Nullable ClassMethod getMethod(Identifier name) { - return EconomicMaps.get(getAllMethods(), name); + return getAllMethods().get(name); } /** Does not include local methods. */ public Iterable getMethods() { - return EconomicMaps.getValues(getAllMethods()); + return getAllMethods().getValues(); } public @Nullable VmClass getSuperclass() { @@ -462,7 +461,7 @@ public void visitMethodDefsTopDown(Consumer visitor) { if (superclass != null) { superclass.visitMethodDefsTopDown(visitor); } - EconomicMaps.getValues(declaredMethods).forEach(visitor); + declaredMethods.getValues().forEach(visitor); } @Override @@ -529,7 +528,7 @@ public EconomicMap getMapToTypedMembers() { private EconomicMap createDelegatingMembers( Function memberNodeFactory) { - var result = EconomicMaps.create(); + var result = EconomicMap.create(); for (var cursor = getAllProperties().getEntries(); cursor.advance(); ) { var property = cursor.getValue(); // Typed->Dynamic conversion: Dynamic objects cannot currently have hidden members. @@ -596,11 +595,9 @@ public PClass export() { if (__pClass == null) { var exportedAnnotations = new ArrayList(); var properties = - CollectionUtils.newLinkedHashMap( - EconomicMaps.size(declaredProperties)); + CollectionUtils.newLinkedHashMap(declaredProperties.size()); var methods = - CollectionUtils.newLinkedHashMap( - EconomicMaps.size(declaredMethods)); + CollectionUtils.newLinkedHashMap(declaredMethods.size()); // set pClass before exporting class members to prevent // infinite recursion in case of cyclic references @@ -626,13 +623,13 @@ public PClass export() { VmUtils.exportAnnotations(annotations, exportedAnnotations); - for (var property : EconomicMaps.getValues(declaredProperties)) { + for (var property : declaredProperties.getValues()) { if (isClassPropertyDefinition(property)) { properties.put(property.getName().toString(), property.export(__pClass)); } } - for (var method : EconomicMaps.getValues(declaredMethods)) { + for (var method : declaredMethods.getValues()) { if (method.isLocal()) continue; methods.put(method.getName().toString(), method.export(__pClass)); } @@ -691,20 +688,19 @@ private boolean isClassPropertyDefinition(ClassProperty declaredProperty) { @TruffleBoundary private UnmodifiableEconomicMap collectAllProperties() { - if (EconomicMaps.isEmpty(declaredProperties)) { - return superclass == null ? EconomicMaps.create() : superclass.getAllProperties(); + if (declaredProperties.isEmpty()) { + return superclass == null ? EconomicMap.create() : superclass.getAllProperties(); } var size = - EconomicMaps.size(declaredProperties) - + (superclass == null ? 0 : EconomicMaps.size(superclass.getAllProperties())); - var result = EconomicMaps.create(size); + declaredProperties.size() + (superclass == null ? 0 : superclass.getAllProperties().size()); + var result = EconomicMap.create(size); if (superclass != null) { - EconomicMaps.putAll(result, superclass.getAllProperties()); + result.putAll(superclass.getAllProperties()); } - for (var property : EconomicMaps.getValues(declaredProperties)) { + for (var property : declaredProperties.getValues()) { if (property.isLocal()) continue; // A property is considered a class property definition @@ -712,8 +708,8 @@ private UnmodifiableEconomicMap collectAllProperties( // Otherwise, it is considered an object property definition, // which means it affects the class prototype but not the class itself. // An example for the latter is when `Module.output` is overridden with `output { ... }`. - if (property.getTypeNode() != null || !EconomicMaps.containsKey(result, property.getName())) { - EconomicMaps.put(result, property.getName(), property); + if (property.getTypeNode() != null || !result.containsKey(property.getName())) { + result.put(property.getName(), property); } } @@ -722,23 +718,22 @@ private UnmodifiableEconomicMap collectAllProperties( @TruffleBoundary private UnmodifiableEconomicMap collectAllMethods() { - if (EconomicMaps.isEmpty(declaredMethods)) { - return superclass == null ? EconomicMaps.create() : superclass.getAllMethods(); + if (declaredMethods.isEmpty()) { + return superclass == null ? EconomicMap.create() : superclass.getAllMethods(); } var size = - EconomicMaps.size(declaredMethods) - + (superclass == null ? 0 : EconomicMaps.size(superclass.getAllMethods())); - var result = EconomicMaps.create(size); + declaredMethods.size() + (superclass == null ? 0 : superclass.getAllMethods().size()); + var result = EconomicMap.create(size); if (superclass != null) { - EconomicMaps.putAll(result, superclass.getAllMethods()); + result.putAll(superclass.getAllMethods()); } - for (var method : EconomicMaps.getValues(declaredMethods)) { + for (var method : declaredMethods.getValues()) { if (method.isLocal()) continue; - EconomicMaps.put(result, method.getName(), method); + result.put(method.getName(), method); } return result; @@ -746,13 +741,13 @@ private UnmodifiableEconomicMap collectAllMethods() { @TruffleBoundary private UnmodifiableEconomicSet collectAllRegularPropertyNames() { - if (EconomicMaps.isEmpty(declaredProperties)) { + if (declaredProperties.isEmpty()) { return superclass == null ? EconomicSet.create() : superclass.getAllRegularPropertyNames(); } var size = superclass == null ? 0 : superclass.getAllRegularPropertyNames().size(); var result = EconomicSet.create(size); - for (var property : EconomicMaps.getValues(declaredProperties)) { + for (var property : declaredProperties.getValues()) { if (!(property.isLocal() || isHiddenProperty(property.getName()) || property.isExternal())) { result.add(property.getName()); } @@ -772,13 +767,13 @@ private UnmodifiableEconomicSet collectAllRegularPropertyNames() { @TruffleBoundary private UnmodifiableEconomicSet collectAllHiddenPropertyNames() { - if (EconomicMaps.isEmpty(declaredProperties)) { + if (declaredProperties.isEmpty()) { return superclass == null ? EconomicSet.create() : superclass.getAllHiddenPropertyNames(); } var size = superclass == null ? 0 : superclass.getAllHiddenPropertyNames().size(); var result = EconomicSet.create(size); - for (var property : EconomicMaps.getValues(declaredProperties)) { + for (var property : declaredProperties.getValues()) { if (property.isHidden()) { result.add(property.getName()); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmDynamic.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmDynamic.java index 2be815bbe..03c563ff9 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmDynamic.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmDynamic.java @@ -18,12 +18,12 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.frame.MaterializedFrame; import java.util.Objects; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.PClassInfo; import org.pkl.core.PObject; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.util.CollectionUtils; -import org.pkl.core.util.EconomicMaps; public final class VmDynamic extends VmObject { private int cachedRegularMemberCount = -1; @@ -33,7 +33,7 @@ private static final class EmptyHolder { new VmDynamic( VmUtils.createEmptyMaterializedFrame(), BaseModule.getDynamicClass().getPrototype(), - EconomicMaps.create(), + EconomicMap.create(), 0); } @@ -75,8 +75,7 @@ public boolean isSequence() { @Override @TruffleBoundary public PObject export() { - var properties = - CollectionUtils.newLinkedHashMap(EconomicMaps.size(cachedValues)); + var properties = CollectionUtils.newLinkedHashMap(cachedValues.size()); iterateMemberValues( (key, member, value) -> { @@ -114,7 +113,6 @@ public boolean equals(Object obj) { if (isHiddenOrLocalProperty(key)) continue; var value = cursor.getValue(); - assert value != null; var otherValue = other.getCachedValue(key); if (!value.equals(otherValue)) return false; } @@ -136,7 +134,6 @@ public int hashCode() { if (isHiddenOrLocalProperty(key)) continue; var value = cursor.getValue(); - assert value != null; result += key.hashCode() ^ value.hashCode(); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmFunction.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmFunction.java index e9cb3f66f..f527e94ee 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmFunction.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmFunction.java @@ -20,10 +20,10 @@ import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.frame.MaterializedFrame; import java.util.function.BiFunction; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.PklRootNode; import org.pkl.core.ast.member.ObjectMember; -import org.pkl.core.util.EconomicMaps; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.util.Nullable; public final class VmFunction extends VmObjectLike { @@ -103,7 +103,7 @@ public boolean hasMember(Object key) { @Override public UnmodifiableEconomicMap getMembers() { - return EconomicMaps.create(); + return EconomicMap.create(); } @Override diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmListing.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmListing.java index 608385ec7..cf06602a5 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmListing.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmListing.java @@ -20,10 +20,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ObjectMember; -import org.pkl.core.util.EconomicMaps; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.util.Nullable; public final class VmListing extends VmListingOrMapping { @@ -32,7 +32,7 @@ private static final class EmptyHolder { new VmListing( VmUtils.createEmptyMaterializedFrame(), BaseModule.getListingClass().getPrototype(), - EconomicMaps.create(), + EconomicMap.create(), 0); } @@ -94,7 +94,7 @@ public VmClass getVmClass() { @Override @TruffleBoundary public List export() { - var properties = new ArrayList<>(EconomicMaps.size(cachedValues)); + var properties = new ArrayList<>(cachedValues.size()); iterateMemberValues( (key, prop, value) -> { diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java index b4d9b2f57..8f407de19 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmListingOrMapping.java @@ -18,15 +18,13 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.nodes.IndirectCallNode; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.PklBugException; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.type.TypeNode; -import org.pkl.core.util.EconomicMaps; -import org.pkl.core.util.EconomicSets; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.EconomicSet; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.util.Nullable; public abstract class VmListingOrMapping> extends VmObject { @@ -39,8 +37,8 @@ public abstract class VmListingOrMapping> private final @Nullable ListingOrMappingTypeCastNode typeCastNode; private final MaterializedFrame typeNodeFrame; - private final EconomicMap cachedMembers = EconomicMaps.create(); - private final EconomicSet checkedMembers = EconomicSets.create(); + private final EconomicMap cachedMembers = EconomicMap.create(); + private final EconomicSet checkedMembers = EconomicSet.create(); public VmListingOrMapping( MaterializedFrame enclosingFrame, @@ -56,7 +54,7 @@ public VmListingOrMapping( } ObjectMember findMember(Object key) { - var member = EconomicMaps.get(cachedMembers, key); + var member = cachedMembers.get(key); if (member != null) { return member; } @@ -75,7 +73,7 @@ ObjectMember findMember(Object key) { @Override public void setCachedValue(Object key, Object value, ObjectMember objectMember) { super.setCachedValue(key, value, objectMember); - EconomicMaps.put(cachedMembers, key, objectMember); + cachedMembers.put(key, objectMember); } @Override @@ -92,7 +90,7 @@ public boolean hasCachedValue(Object key) { var memberValue = delegate.getCachedValue(key); // if this object member appears inside `checkedMembers`, we have already checked its type // and can safely return it. - if (EconomicSets.contains(checkedMembers, key)) { + if (checkedMembers.contains(key)) { return memberValue; } if (memberValue == null) { @@ -103,10 +101,10 @@ public boolean hasCachedValue(Object key) { var objectMember = findMember(key); var ret = typecastObjectMember(objectMember, memberValue, IndirectCallNode.getUncached()); if (ret != memberValue) { - EconomicMaps.put(cachedValues, key, ret); + cachedValues.put(key, ret); } else { // optimization: don't add to own cached values if typecast results in the same value - EconomicSets.add(checkedMembers, key); + checkedMembers.add(key); } return ret; } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmMapping.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmMapping.java index 10f050f4c..002a95573 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmMapping.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmMapping.java @@ -20,11 +20,11 @@ import java.util.Map; import java.util.Objects; import javax.annotation.concurrent.GuardedBy; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.member.ListingOrMappingTypeCastNode; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.util.CollectionUtils; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.LateInit; public final class VmMapping extends VmListingOrMapping { @@ -39,7 +39,7 @@ private static final class EmptyHolder { new VmMapping( VmUtils.createEmptyMaterializedFrame(), BaseModule.getMappingClass().getPrototype(), - EconomicMaps.create()); + EconomicMap.create()); } public static VmMapping empty() { @@ -104,7 +104,7 @@ public VmSet getAllKeys() { @Override @TruffleBoundary public Map export() { - var properties = CollectionUtils.newLinkedHashMap(EconomicMaps.size(cachedValues)); + var properties = CollectionUtils.newLinkedHashMap(cachedValues.size()); iterateMemberValues( (key, prop, value) -> { diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java index c7d86a683..4b601f9f1 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmObject.java @@ -20,11 +20,10 @@ import com.oracle.truffle.api.frame.MaterializedFrame; import java.util.*; import java.util.function.BiFunction; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.util.CollectionUtils; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; /** Corresponds to `pkl.base#Object`. */ @@ -53,7 +52,7 @@ public VmObject( MaterializedFrame enclosingFrame, @Nullable VmObject parent, UnmodifiableEconomicMap members) { - this(enclosingFrame, parent, members, EconomicMaps.create()); + this(enclosingFrame, parent, members, EconomicMap.create()); } public final void lateInitParent(VmObject parent) { @@ -68,12 +67,12 @@ public final void lateInitParent(VmObject parent) { @Override public final boolean hasMember(Object key) { - return EconomicMaps.containsKey(members, key); + return members.containsKey(key); } @Override public final @Nullable ObjectMember getMember(Object key) { - return EconomicMaps.get(members, key); + return members.get(key); } @Override @@ -83,17 +82,17 @@ public final UnmodifiableEconomicMap getMembers() { @Override public @Nullable Object getCachedValue(Object key) { - return EconomicMaps.get(cachedValues, key); + return cachedValues.get(key); } @Override public void setCachedValue(Object key, Object value, ObjectMember objectMember) { - EconomicMaps.put(cachedValues, key, value); + cachedValues.put(key, value); } @Override public boolean hasCachedValue(Object key) { - return EconomicMaps.containsKey(cachedValues, key); + return cachedValues.containsKey(key); } @Override @@ -160,7 +159,7 @@ public final void force(boolean allowUndefinedValues, boolean recurse) { try { for (VmObjectLike owner = this; owner != null; owner = owner.getParent()) { - var cursor = EconomicMaps.getEntries(owner.getMembers()); + var cursor = owner.getMembers().getEntries(); var clazz = owner.getVmClass(); while (cursor.advance()) { var memberKey = cursor.getKey(); @@ -208,7 +207,7 @@ public final String toString() { */ @TruffleBoundary protected final Map exportMembers() { - var result = CollectionUtils.newLinkedHashMap(EconomicMaps.size(cachedValues)); + var result = CollectionUtils.newLinkedHashMap(cachedValues.size()); iterateMemberValues( (key, member, value) -> { diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectBuilder.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectBuilder.java index f2cc06d92..f0069a73a 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectBuilder.java @@ -15,11 +15,10 @@ */ package org.pkl.core.runtime; -import org.graalvm.collections.EconomicMap; import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.member.SharedMemberNode; -import org.pkl.core.util.EconomicMaps; +import org.pkl.core.collection.EconomicMap; /** A builder for {@link VmObject}s whose {@link ObjectMember}s are determined at run time. */ public final class VmObjectBuilder { @@ -27,26 +26,26 @@ public final class VmObjectBuilder { private int elementCount = 0; public VmObjectBuilder() { - members = EconomicMaps.create(); + members = EconomicMap.create(); } public VmObjectBuilder(int initialSize) { - members = EconomicMaps.create(initialSize); + // for economic maps, size == capacity + members = EconomicMap.create(initialSize); } public VmObjectBuilder addProperty(Identifier name, Object value) { - EconomicMaps.put(members, name, VmUtils.createSyntheticObjectProperty(name, "", value)); + members.put(name, VmUtils.createSyntheticObjectProperty(name, "", value)); return this; } public VmObjectBuilder addElement(Object value) { - EconomicMaps.put( - members, (long) elementCount++, VmUtils.createSyntheticObjectElement("", value)); + members.put((long) elementCount++, VmUtils.createSyntheticObjectElement("", value)); return this; } public VmObjectBuilder addEntry(Object key, Object value) { - EconomicMaps.put(members, key, VmUtils.createSyntheticObjectEntry("", value)); + members.put(key, VmUtils.createSyntheticObjectEntry("", value)); return this; } @@ -55,7 +54,7 @@ public VmObjectBuilder addEntry(Object key, SharedMemberNode valueNode) { new ObjectMember( valueNode.getSourceSection(), valueNode.getHeaderSection(), VmModifier.ENTRY, null, ""); entry.initMemberNode(valueNode); - EconomicMaps.put(members, key, entry); + members.put(key, entry); return this; } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectLike.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectLike.java index d665162bf..8c2d84b4d 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectLike.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmObjectLike.java @@ -18,8 +18,8 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.frame.MaterializedFrame; import java.util.function.BiFunction; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.util.Nullable; /** diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java index 7bbc1987c..3acda70b1 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmTyped.java @@ -18,14 +18,13 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.frame.MaterializedFrame; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.UnmodifiableEconomicMap; import org.pkl.core.Composite; import org.pkl.core.PModule; import org.pkl.core.PObject; import org.pkl.core.ast.expression.unary.ImportNode; import org.pkl.core.ast.member.ObjectMember; -import org.pkl.core.util.EconomicMaps; +import org.pkl.core.collection.EconomicMap; +import org.pkl.core.collection.UnmodifiableEconomicMap; import org.pkl.core.util.LateInit; import org.pkl.core.util.Nullable; @@ -48,11 +47,11 @@ public void lateInitVmClass(VmClass clazz) { } public void addProperty(ObjectMember property) { - EconomicMaps.put((EconomicMap) members, property.getName(), property); + ((EconomicMap) members).put(property.getName(), property); } public void addProperties(UnmodifiableEconomicMap properties) { - EconomicMaps.putAll((EconomicMap) members, properties); + ((EconomicMap) members).putAll(properties); } public VmClass getVmClass() { diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java index 469090718..b03da9311 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java @@ -51,13 +51,13 @@ import org.pkl.core.ast.member.*; import org.pkl.core.ast.type.TypeNode; import org.pkl.core.ast.type.UnresolvedTypeNode; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.module.ModuleKey; import org.pkl.core.module.ModuleKeys; import org.pkl.core.module.ResolvedModuleKey; import org.pkl.core.parser.LexParseException; import org.pkl.core.parser.Parser; import org.pkl.core.parser.antlr.PklParser.ExprContext; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; public final class VmUtils { @@ -90,7 +90,7 @@ public final class VmUtils { private VmUtils() {} static VmTyped createEmptyModule() { - return new VmTyped(createEmptyMaterializedFrame(), null, null, EconomicMaps.create()); + return new VmTyped(createEmptyMaterializedFrame(), null, null, EconomicMap.create()); } @TruffleBoundary diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/VmObjectFactory.java b/pkl-core/src/main/java/org/pkl/core/stdlib/VmObjectFactory.java index 0d89e5c54..de4294d99 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/VmObjectFactory.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/VmObjectFactory.java @@ -19,20 +19,19 @@ import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.VirtualFrame; import java.util.function.Supplier; -import org.graalvm.collections.EconomicMap; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.member.TypeCheckedPropertyNodeGen; import org.pkl.core.ast.member.UntypedObjectMemberNode; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.runtime.*; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.IoUtils; import org.pkl.core.util.Nullable; public final class VmObjectFactory { private final Supplier classSupplier; - private final EconomicMap members = EconomicMaps.create(); + private final EconomicMap members = EconomicMap.create(); // not static to avoid compile-time evaluation by native-image private final boolean isPropertyTypeChecked = IoUtils.isTestMode(); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/IntSeqNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/IntSeqNodes.java index 8ff87e72d..23f4e44d4 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/IntSeqNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/IntSeqNodes.java @@ -21,13 +21,13 @@ import com.oracle.truffle.api.nodes.Node; import org.pkl.core.ast.lambda.*; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.runtime.*; import org.pkl.core.stdlib.ExternalMethod0Node; import org.pkl.core.stdlib.ExternalMethod1Node; import org.pkl.core.stdlib.ExternalMethod2Node; import org.pkl.core.stdlib.ExternalPropertyNode; import org.pkl.core.stdlib.PklName; -import org.pkl.core.util.EconomicMaps; public final class IntSeqNodes { private IntSeqNodes() {} @@ -109,15 +109,13 @@ public abstract static class toListing extends ExternalMethod0Node { @Specialization @TruffleBoundary protected VmListing eval(VmIntSeq self) { - var result = EconomicMaps.create(); + var result = EconomicMap.create(); var iterator = self.iterator(); long idx = 0; while (iterator.hasNext()) { - EconomicMaps.put( - result, - idx, - VmUtils.createSyntheticObjectElement(String.valueOf(idx), iterator.nextLong())); + result.put( + idx, VmUtils.createSyntheticObjectElement(String.valueOf(idx), iterator.nextLong())); idx += 1; } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListNodes.java index 63d2d5006..5f5e782f4 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/ListNodes.java @@ -23,12 +23,12 @@ import org.pkl.core.ast.internal.IsInstanceOfNode; import org.pkl.core.ast.internal.IsInstanceOfNodeGen; import org.pkl.core.ast.lambda.*; +import org.pkl.core.collection.EconomicSet; import org.pkl.core.runtime.*; import org.pkl.core.stdlib.*; import org.pkl.core.stdlib.base.CollectionNodes.CompareByNode; import org.pkl.core.stdlib.base.CollectionNodes.CompareNode; import org.pkl.core.stdlib.base.CollectionNodes.CompareWithNode; -import org.pkl.core.util.EconomicSets; // duplication between ListNodes and SetNodes is "intentional" // (sharing nodes between VmCollection subtypes results in @@ -481,10 +481,10 @@ protected VmList eval(VmList self, VmClass clazz) { public abstract static class isDistinct extends ExternalPropertyNode { @Specialization protected boolean eval(VmList self) { - var visited = EconomicSets.create(); + var visited = EconomicSet.create(); for (var elem : self) { - if (!EconomicSets.add(visited, elem)) return false; + if (!visited.add(elem)) return false; } LoopNode.reportLoopCount(this, self.getLength()); @@ -497,10 +497,11 @@ public abstract static class isDistinctBy extends ExternalMethod1Node { @Specialization protected boolean eval(VmList self, VmFunction function) { - var visited = EconomicSets.create(); + var visited = EconomicSet.create(); for (var elem : self) { - if (!EconomicSets.add(visited, applyLambdaNode.execute(function, elem))) return false; + Object element = applyLambdaNode.execute(function, elem); + if (!visited.add(element)) return false; } LoopNode.reportLoopCount(this, self.getLength()); @@ -512,10 +513,10 @@ public abstract static class distinct extends ExternalPropertyNode { @Specialization protected VmList eval(VmList self) { var builder = self.builder(); - var visited = EconomicSets.create(); + var visited = EconomicSet.create(); for (var elem : self) { - if (EconomicSets.add(visited, elem)) builder.add(elem); + if (visited.add(elem)) builder.add(elem); } LoopNode.reportLoopCount(this, self.getLength()); @@ -529,10 +530,11 @@ public abstract static class distinctBy extends ExternalMethod1Node { @Specialization protected VmList eval(VmList self, VmFunction function) { var builder = self.builder(); - var visited = EconomicSets.create(); + var visited = EconomicSet.create(); for (var elem : self) { - if (EconomicSets.add(visited, applyLambdaNode.execute(function, elem))) builder.add(elem); + Object element = applyLambdaNode.execute(function, elem); + if (visited.add(element)) builder.add(elem); } LoopNode.reportLoopCount(this, self.getLength()); diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/MappingNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/MappingNodes.java index ea00cbcc4..5277ca2e9 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/MappingNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/MappingNodes.java @@ -28,7 +28,6 @@ import org.pkl.core.stdlib.ExternalMethod1Node; import org.pkl.core.stdlib.ExternalMethod2Node; import org.pkl.core.stdlib.ExternalPropertyNode; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.MutableBoolean; import org.pkl.core.util.MutableLong; import org.pkl.core.util.MutableReference; @@ -41,7 +40,7 @@ public abstract static class isEmpty extends ExternalPropertyNode { @TruffleBoundary protected boolean eval(VmMapping self) { for (VmObjectLike curr = self; curr != null; curr = curr.getParent()) { - var cursor = EconomicMaps.getEntries(curr.getMembers()); + var cursor = curr.getMembers().getEntries(); while (cursor.advance()) { if (!(cursor.getKey() instanceof Identifier)) return false; } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/PropertiesRendererNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/PropertiesRendererNodes.java index 9d9b52897..45a8370a6 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/PropertiesRendererNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/PropertiesRendererNodes.java @@ -17,6 +17,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmDataSize; import org.pkl.core.runtime.VmDuration; @@ -38,7 +39,6 @@ import org.pkl.core.stdlib.AbstractRenderer; import org.pkl.core.stdlib.ExternalMethod1Node; import org.pkl.core.stdlib.PklConverter; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.MutableBoolean; import org.pkl.core.util.properties.PropertiesUtils; @@ -277,7 +277,7 @@ private void visitKeyedValue(Object value) { new VmListing( VmUtils.createEmptyMaterializedFrame(), dynamic, - EconomicMaps.create(), + EconomicMap.create(), dynamic.getLength()); visit(converter.convert(newValue, currPath)); } else { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/json/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/json/ParserNodes.java index 1321674dc..279c7fc85 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/json/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/json/ParserNodes.java @@ -20,13 +20,12 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.IndirectCallNode; import java.util.*; -import org.graalvm.collections.EconomicMap; import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.runtime.*; import org.pkl.core.stdlib.ExternalMethod1Node; import org.pkl.core.stdlib.PklConverter; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.Nullable; import org.pkl.core.util.json.JsonHandler; import org.pkl.core.util.json.JsonParser; @@ -116,7 +115,7 @@ public void endNumber(String string) { @Override public EconomicMap startArray() { currPath.push(VmValueConverter.WILDCARD_ELEMENT); - return EconomicMaps.create(); + return EconomicMap.create(); } @Override @@ -127,14 +126,14 @@ public void endArray(@Nullable EconomicMap members) { VmUtils.createEmptyMaterializedFrame(), BaseModule.getListingClass().getPrototype(), members, - EconomicMaps.size(members)); + members.size()); currPath.pop(); } @Override public void endArrayValue(@Nullable EconomicMap members) { assert members != null; - var size = EconomicMaps.size(members); + var size = members.size(); var member = new ObjectMember( VmUtils.unavailableSourceSection(), @@ -143,12 +142,12 @@ public void endArrayValue(@Nullable EconomicMap members) { null, String.valueOf(size)); member.initConstantValue(converter.convert(value, currPath)); - EconomicMaps.put(members, (long) size, member); + members.put((long) size, member); } @Override public EconomicMap startObject() { - return EconomicMaps.create(); + return EconomicMap.create(); } @Override @@ -187,7 +186,7 @@ public void endObjectValue(@Nullable EconomicMap members, useMapping ? null : (Identifier) memberName, "generated"); member.initConstantValue(converter.convert(value, currPath)); - EconomicMaps.put(members, memberName, member); + members.put(memberName, member); currPath.pop(); } } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/test/report/JUnitReport.java b/pkl-core/src/main/java/org/pkl/core/stdlib/test/report/JUnitReport.java index 84a82ec5a..8f514ef3a 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/test/report/JUnitReport.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/test/report/JUnitReport.java @@ -19,12 +19,12 @@ import java.io.Writer; import java.util.ArrayList; import java.util.function.Consumer; -import org.graalvm.collections.EconomicMap; import org.pkl.core.TestResults; import org.pkl.core.TestResults.Error; import org.pkl.core.TestResults.TestResult; import org.pkl.core.TestResults.TestSectionResults; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.runtime.BaseModule; import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.VmDynamic; @@ -34,7 +34,6 @@ import org.pkl.core.runtime.XmlModule; import org.pkl.core.stdlib.PklConverter; import org.pkl.core.stdlib.xml.RendererNodes.Renderer; -import org.pkl.core.util.EconomicMaps; public final class JUnitReport implements TestReport { @@ -156,15 +155,17 @@ private VmDynamic buildXmlElement(String name, VmMapping attributes, VmDynamic.. private VmDynamic buildXmlElement( String name, VmMapping attributes, Consumer> gen) { - EconomicMap members = - EconomicMaps.of( - Identifier.IS_XML_ELEMENT, - VmUtils.createSyntheticObjectProperty(Identifier.IS_XML_ELEMENT, "", true), - Identifier.NAME, VmUtils.createSyntheticObjectProperty(Identifier.NAME, "", name), - Identifier.ATTRIBUTES, - VmUtils.createSyntheticObjectProperty(Identifier.ATTRIBUTES, "", attributes), - Identifier.IS_BLOCK_FORMAT, - VmUtils.createSyntheticObjectProperty(Identifier.IS_BLOCK_FORMAT, "", true)); + var members = EconomicMap.create(4); + members.put( + Identifier.IS_XML_ELEMENT, + VmUtils.createSyntheticObjectProperty(Identifier.IS_XML_ELEMENT, "", true)); + members.put(Identifier.NAME, VmUtils.createSyntheticObjectProperty(Identifier.NAME, "", name)); + members.put( + Identifier.ATTRIBUTES, + VmUtils.createSyntheticObjectProperty(Identifier.ATTRIBUTES, "", attributes)); + members.put( + Identifier.IS_BLOCK_FORMAT, + VmUtils.createSyntheticObjectProperty(Identifier.IS_BLOCK_FORMAT, "", true)); gen.accept(members); return new VmDynamic( VmUtils.createEmptyMaterializedFrame(), @@ -174,7 +175,7 @@ private VmDynamic buildXmlElement( } private VmMapping buildAttributes(Object... attributes) { - EconomicMap attrs = EconomicMaps.create(attributes.length); + EconomicMap attrs = EconomicMap.create(attributes.length); for (int i = 0; i < attributes.length; i += 2) { attrs.put( attributes[i], @@ -193,7 +194,7 @@ private VmTyped makeCdata(String text) { // HACK: The property identifier here has to be `null` instead of `Identifier.TEXT` or // a `Invalid sharing of AST nodes detected` error will be thrown. EconomicMap attrs = - EconomicMaps.of(Identifier.TEXT, VmUtils.createSyntheticObjectProperty(null, "", text)); + EconomicMap.of(Identifier.TEXT, VmUtils.createSyntheticObjectProperty(null, "", text)); return new VmTyped(VmUtils.createEmptyMaterializedFrame(), clazz.getPrototype(), clazz, attrs); } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/yaml/ParserNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/yaml/ParserNodes.java index fbe28890b..c83770678 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/yaml/ParserNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/yaml/ParserNodes.java @@ -24,13 +24,12 @@ import java.net.URI; import java.util.*; import java.util.regex.Pattern; -import org.graalvm.collections.EconomicMap; import org.pkl.core.ast.VmModifier; import org.pkl.core.ast.member.ObjectMember; +import org.pkl.core.collection.EconomicMap; import org.pkl.core.runtime.*; import org.pkl.core.stdlib.ExternalMethod1Node; import org.pkl.core.stdlib.PklConverter; -import org.pkl.core.util.EconomicMaps; import org.pkl.core.util.yaml.snake.YamlUtils; import org.snakeyaml.engine.v2.api.ConstructNode; import org.snakeyaml.engine.v2.api.Load; @@ -322,7 +321,7 @@ private class ConstructSeq implements ConstructNode { public VmListing construct(Node node) { var sequenceNode = (SequenceNode) node; var size = sequenceNode.getValue().size(); - var members = EconomicMaps.create(size); + var members = EconomicMap.create(size); var result = new VmListing( @@ -357,7 +356,7 @@ private void addMembers(SequenceNode node, VmListing listing) { new ObjectMember( sourceSection, sourceSection, VmModifier.ELEMENT, null, String.valueOf(index)); member.initConstantValue(converter.convert(constructObject(childNode), currPath)); - EconomicMaps.put(members, index++, member); + members.put(index++, member); } currPath.pop(); } @@ -368,7 +367,7 @@ private class ConstructSet implements ConstructNode { public VmListing construct(Node node) { var mappingNode = (MappingNode) node; var size = mappingNode.getValue().size(); - var members = EconomicMaps.create(size); + var members = EconomicMap.create(size); var result = new VmListing( @@ -405,7 +404,7 @@ private void addMembers(MappingNode node, VmListing listing) { new ObjectMember( sourceSection, sourceSection, VmModifier.ELEMENT, null, String.valueOf(index)); member.initConstantValue(converter.convert(constructObject(keyNode), currPath)); - EconomicMaps.put(members, index++, member); + members.put(index++, member); } currPath.pop(); } @@ -416,7 +415,7 @@ private class ConstructMap implements ConstructNode { public VmObject construct(Node node) { var mappingNode = (MappingNode) node; var size = mappingNode.getValue().size(); - var members = EconomicMaps.create(size); + var members = EconomicMap.create(size); VmObject result; if (useMapping) { @@ -486,7 +485,7 @@ private void addMembers(MappingNode node, VmObject object) { member.initConstantValue(convertedValue); currPath.pop(); - EconomicMaps.put(members, memberName != null ? memberName : convertedKey, member); + members.put(memberName != null ? memberName : convertedKey, member); } } } diff --git a/pkl-core/src/main/java/org/pkl/core/util/EconomicMaps.java b/pkl-core/src/main/java/org/pkl/core/util/EconomicMaps.java deleted file mode 100644 index ab462c2e4..000000000 --- a/pkl-core/src/main/java/org/pkl/core/util/EconomicMaps.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pkl.core.util; - -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import java.util.Objects; -import java.util.function.BiFunction; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.MapCursor; -import org.graalvm.collections.UnmodifiableEconomicMap; -import org.graalvm.collections.UnmodifiableMapCursor; - -/** - * Puts {@link TruffleBoundary}s on {@link EconomicMap} methods and provides some added - * functionality. - */ -public final class EconomicMaps { - private EconomicMaps() {} - - @TruffleBoundary - public static EconomicMap create() { - return EconomicMap.create(); - } - - @TruffleBoundary - public static EconomicMap create(int initialSize) { - // for economic maps, size == capacity - return EconomicMap.create(initialSize); - } - - @TruffleBoundary - public static EconomicMap of(K key, V value) { - var result = EconomicMap.create(1); - result.put(key, value); - return result; - } - - @TruffleBoundary - public static EconomicMap of(K key1, V value1, K key2, V value2) { - var result = EconomicMap.create(2); - result.put(key1, value1); - result.put(key2, value2); - return result; - } - - @TruffleBoundary - public static EconomicMap of(K key1, V value1, K key2, V value2, K key3, V value3) { - var result = EconomicMap.create(3); - result.put(key1, value1); - result.put(key2, value2); - result.put(key3, value3); - return result; - } - - @TruffleBoundary - public static EconomicMap of( - K key1, V value1, K key2, V value2, K key3, V value3, K key4, V value4) { - var result = EconomicMap.create(4); - result.put(key1, value1); - result.put(key2, value2); - result.put(key3, value3); - result.put(key4, value4); - return result; - } - - @TruffleBoundary - public static @Nullable V get(UnmodifiableEconomicMap self, K key) { - return self.get(key); - } - - @TruffleBoundary - public static @Nullable V get(UnmodifiableEconomicMap self, K key, V defaultValue) { - return self.get(key, defaultValue); - } - - @TruffleBoundary - public static boolean containsKey(UnmodifiableEconomicMap self, K key) { - return self.containsKey(key); - } - - @TruffleBoundary - public static int size(UnmodifiableEconomicMap self) { - return self.size(); - } - - @TruffleBoundary - public static boolean isEmpty(UnmodifiableEconomicMap self) { - return self.isEmpty(); - } - - @TruffleBoundary - public static Iterable getValues(UnmodifiableEconomicMap self) { - return self.getValues(); - } - - @TruffleBoundary - public static Iterable getKeys(UnmodifiableEconomicMap self) { - return self.getKeys(); - } - - @TruffleBoundary - public static UnmodifiableMapCursor getEntries(UnmodifiableEconomicMap self) { - return self.getEntries(); - } - - @TruffleBoundary - public static @Nullable V put(EconomicMap self, K key, @Nullable V value) { - return self.put(key, value); - } - - @TruffleBoundary - public static void putAll(EconomicMap self, EconomicMap other) { - self.putAll(other); - } - - @TruffleBoundary - public static void putAll( - EconomicMap self, UnmodifiableEconomicMap other) { - - self.putAll(other); - } - - @TruffleBoundary - public static void clear(EconomicMap self) { - self.clear(); - } - - @TruffleBoundary - public static V removeKey(EconomicMap self, K key) { - return self.removeKey(key); - } - - @TruffleBoundary - public static MapCursor getEntries(EconomicMap self) { - return self.getEntries(); - } - - @TruffleBoundary - public static void replaceAll( - EconomicMap self, BiFunction function) { - self.replaceAll(function); - } - - // inspired by java.util.AbstractMap#equals - @TruffleBoundary - @SuppressWarnings({"unchecked", "rawtypes"}) - public static boolean equals(UnmodifiableEconomicMap map1, UnmodifiableEconomicMap map2) { - if (map1 == map2) return true; - - if (map1.size() != map2.size()) return false; - - for (var cursor = map1.getEntries(); cursor.advance(); ) { - var key1 = cursor.getKey(); - var value1 = cursor.getValue(); - var value2 = map2.get(key1); - if (value2 == null) { - if (value1 != null || !map2.containsKey(key1)) return false; - } else { - if (!value2.equals(value1)) return false; - } - } - - return true; - } - - // inspired by java.util.AbstractMap#hashCode - @TruffleBoundary - public static int hashCode(UnmodifiableEconomicMap map) { - var result = 0; - for (var cursor = map.getEntries(); cursor.advance(); ) { - result += (cursor.getKey().hashCode() ^ Objects.hashCode(cursor.getValue())); - } - return result; - } -} diff --git a/pkl-core/src/test/kotlin/org/pkl/core/project/ProjectDepsTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/project/ProjectDepsTest.kt index 447077e5d..5bf126d11 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/project/ProjectDepsTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/project/ProjectDepsTest.kt @@ -19,10 +19,10 @@ import java.io.ByteArrayOutputStream import java.nio.file.Path import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.pkl.core.collection.EconomicMap import org.pkl.core.packages.Checksums import org.pkl.core.packages.Dependency import org.pkl.core.packages.PackageUri -import org.pkl.core.util.EconomicMaps class ProjectDepsTest { private val projectDepsStr = @@ -50,7 +50,7 @@ class ProjectDepsTest { private val projectDeps = let { val projectDepsMap = - EconomicMaps.of( + EconomicMap.of( CanonicalPackageUri.of("package://localhost:0/birds@0"), Dependency.RemoteDependency( PackageUri.create("package://localhost:0/birds@0.5.0"),