From 7a12edd8d724fc36d69331ab529f097dce1ba2d5 Mon Sep 17 00:00:00 2001 From: translatenix <119817707+translatenix@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:12:59 -0800 Subject: [PATCH] Vendor org.graalvm.collections Motivation: Package org.graalvm.collections is used throughout the Pkl codebase. It is most heavily used in the Truffle interpreter, which requires putting most collection methods behind @TruffleBoundary. At the moment, this is done by wrapping collection methods with static methods declared in classes EconomicMaps and EconomicSets. However, static wrapper methods are inconvenient to use, decrease code readability, add some overhead, and are easy to forget about. Changes: - vendor package org.graalvm.collections into org.pkl.core.collection - org.graalvm.collections is licensed under: The Universal Permissive License (UPL), Version 1.0 - vendored version: https://github.com/oracle/graal/tree/graal-23.0.3/sdk/src/org.graalvm.collections/src/org/graalvm/collections - add package-info.java with original license - annotate most public methods with @TruffleBoundary - add @Nullable annotations - switching to JSpecify will enable more accurate nullability checks - remove prefix tree classes (not used in Pkl) - make no other code changes to simplify review/updates - inline and remove EconomicMaps/Sets wrapper methods - replace usages of EconomicMaps.equals/hashCode with EconomicMapUtil.equals/hashCode - fix ProjectDeps.hashCode() Result: Cleaner, safer, and more efficient code. --- .../org/pkl/core/ast/builder/AstBuilder.java | 25 +- .../generator/GeneratorElementNode.java | 3 +- .../generator/GeneratorEntryNode.java | 3 +- .../generator/GeneratorMemberNode.java | 7 +- .../GeneratorPredicateMemberNode.java | 11 +- .../generator/GeneratorPropertyNode.java | 3 +- .../generator/GeneratorSpreadNode.java | 15 +- .../generator/WriteForVariablesNode.java | 5 +- .../expression/literal/AmendModuleNode.java | 2 +- .../literal/ConstantEntriesLiteralNode.java | 2 +- .../literal/ElementsEntriesLiteralNode.java | 12 +- .../literal/ElementsLiteralNode.java | 11 +- .../literal/EntriesLiteralNode.java | 17 +- .../literal/PropertiesLiteralNode.java | 2 +- .../literal/SpecializedObjectLiteralNode.java | 19 +- .../ast/expression/unary/ReadGlobNode.java | 2 +- .../org/pkl/core/ast/member/ClassNode.java | 2 +- .../java/org/pkl/core/ast/type/TypeNode.java | 18 +- .../org/pkl/core/collection/EconomicMap.java | 275 ++++++ .../pkl/core/collection/EconomicMapImpl.java | 869 ++++++++++++++++++ .../pkl/core/collection/EconomicMapUtil.java | 151 +++ .../pkl/core/collection/EconomicMapWrap.java | 173 ++++ .../org/pkl/core/collection/EconomicSet.java | 196 ++++ .../org/pkl/core/collection/EmptyMap.java | 131 +++ .../org/pkl/core/collection/Equivalence.java | 114 +++ .../org/pkl/core/collection/MapCursor.java | 47 + .../java/org/pkl/core/collection/Pair.java | 155 ++++ .../collection/UnmodifiableEconomicMap.java | 106 +++ .../collection/UnmodifiableEconomicSet.java | 69 ++ .../UnmodifiableMapCursor.java} | 45 +- .../org/pkl/core/collection/package-info.java | 50 + .../java/org/pkl/core/module/PathElement.java | 7 +- .../module/ProjectDependenciesManager.java | 7 +- .../pkl/core/packages/PackageResolvers.java | 13 +- .../project/ProjectDependenciesResolver.java | 14 +- .../org/pkl/core/project/ProjectDeps.java | 14 +- .../org/pkl/core/project/ProjectPackager.java | 2 +- .../java/org/pkl/core/repl/ReplServer.java | 6 +- .../pkl/core/runtime/FileSystemManager.java | 5 +- .../core/runtime/MemberLookupSuggestions.java | 3 +- .../java/org/pkl/core/runtime/ModuleInfo.java | 3 +- .../java/org/pkl/core/runtime/TestRunner.java | 4 +- .../java/org/pkl/core/runtime/VmClass.java | 83 +- .../java/org/pkl/core/runtime/VmDynamic.java | 11 +- .../java/org/pkl/core/runtime/VmFunction.java | 6 +- .../java/org/pkl/core/runtime/VmListing.java | 8 +- .../pkl/core/runtime/VmListingOrMapping.java | 22 +- .../java/org/pkl/core/runtime/VmMapping.java | 8 +- .../java/org/pkl/core/runtime/VmObject.java | 21 +- .../org/pkl/core/runtime/VmObjectBuilder.java | 17 +- .../org/pkl/core/runtime/VmObjectLike.java | 2 +- .../java/org/pkl/core/runtime/VmTyped.java | 9 +- .../java/org/pkl/core/runtime/VmUtils.java | 4 +- .../org/pkl/core/stdlib/VmObjectFactory.java | 5 +- .../org/pkl/core/stdlib/base/IntSeqNodes.java | 10 +- .../org/pkl/core/stdlib/base/ListNodes.java | 20 +- .../pkl/core/stdlib/base/MappingNodes.java | 3 +- .../stdlib/base/PropertiesRendererNodes.java | 4 +- .../org/pkl/core/stdlib/json/ParserNodes.java | 15 +- .../core/stdlib/test/report/JUnitReport.java | 27 +- .../org/pkl/core/stdlib/yaml/ParserNodes.java | 15 +- .../java/org/pkl/core/util/EconomicMaps.java | 188 ---- .../org/pkl/core/project/ProjectDepsTest.kt | 4 +- 63 files changed, 2608 insertions(+), 492 deletions(-) create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/EconomicMap.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/EconomicMapImpl.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/EconomicMapUtil.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/EconomicMapWrap.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/EconomicSet.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/EmptyMap.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/Equivalence.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/MapCursor.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/Pair.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableEconomicMap.java create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/UnmodifiableEconomicSet.java rename pkl-core/src/main/java/org/pkl/core/{util/EconomicSets.java => collection/UnmodifiableMapCursor.java} (54%) create mode 100644 pkl-core/src/main/java/org/pkl/core/collection/package-info.java delete mode 100644 pkl-core/src/main/java/org/pkl/core/util/EconomicMaps.java 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"),