diff --git a/src/main/java/org/rumbledb/context/StaticContext.java b/src/main/java/org/rumbledb/context/StaticContext.java index 60f838b6b1..220d12b34c 100644 --- a/src/main/java/org/rumbledb/context/StaticContext.java +++ b/src/main/java/org/rumbledb/context/StaticContext.java @@ -21,11 +21,12 @@ package org.rumbledb.context; import java.net.URI; -import java.util.LinkedHashSet; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import org.rumbledb.config.RumbleRuntimeConfiguration; @@ -41,22 +42,48 @@ import org.rumbledb.types.ItemType; import org.rumbledb.types.SequenceType; +import lombok.Getter; +import lombok.Setter; + public class StaticContext { - private Map inScopeVariables; - private Map staticallyKnownNamespaces; + @Getter + private final Map inScopeVariables = new HashMap<>(); + + private final Map staticallyKnownNamespaces = new HashMap<>(); private UserDefinedFunctionExecutionModes userDefinedFunctionExecutionModes; - private InScopeSchemaTypes inScopeSchemaTypes; + + @Getter + private InScopeSchemaTypes inScopeSchemaTypes = new InScopeSchemaTypes(); + + @Setter private String queryLanguage; + + @Getter private StaticContext parent; + private URI staticBaseURI; - private boolean emptySequenceOrderLeast; - private boolean boundarySpacePreserve; - private SerializationParameters serializationParameters; - private Set explicitSerializationParameterNames; + private boolean emptySequenceOrderLeast = true; + private boolean boundarySpacePreserve = true; + + /** + * -- GETTER -- + * Returns the default serialization parameters stored in the static context. + * Spec references: + *
    + *
  • XQuery 3.1 Static Context Components (link: + * https://www.w3.org/TR/xquery-31/#id-xq-static-context-components)
  • + *
  • Serialization 3.1 — Serialization Parameters (link: + * https://www.w3.org/TR/xslt-xquery-serialization-31/#serparam)
  • + *
+ */ + @Getter + private SerializationParameters serializationParameters = SerializationParameters.defaults(); + + private final Set explicitSerializationParameterNames = new LinkedHashSet<>(); private boolean isQuerySideEffecting; - private Set staticallyKnownCollations; - private String defaultCollation; + private final Set staticallyKnownCollations = CollationCatalogue.defaultStaticallyKnownCollations(); + private String defaultCollation = CollationCatalogue.CODEPOINT_COLLATION; /** * XQuery {@code declare default function namespace}; when null, unprefixed function names use @@ -64,112 +91,57 @@ public class StaticContext { */ private String defaultFunctionNamespaceUri; + @Getter + @Setter private SequenceType contextItemStaticType; - private Map staticallyKnownFunctionSignatures; - private static final Map defaultBindings; - - private DecimalFormatDefinition defaultDecimalFormat; - private Map decimalFormats; - + private final Map staticallyKnownFunctionSignatures = + new HashMap<>(); + private static final Map DEFAULT_BINDINGS = Map.ofEntries( + Map.entry("local", Name.LOCAL_NS), + Map.entry("fn", Name.FN_NS), + Map.entry("math", Name.MATH_NS), + Map.entry("map", Name.MAP_NS), + Map.entry("array", Name.ARRAY_NS), + Map.entry("xs", Name.XS_NS), + Map.entry("xsi", Name.XSI_NS), + Map.entry("xml", Name.XML_NS), + Map.entry("jn", Name.JN_NS), + Map.entry("js", Name.JS_NS), + Map.entry("err", Name.ERROR_NS), + Map.entry("an", Name.JSONIQ_ANNOTATIONS_NS) + ); + + private DecimalFormatDefinition defaultDecimalFormat = DecimalFormatDefinition.defaultInstance(); + private final Map decimalFormats = new HashMap<>(); + + @Getter + @Setter private int currentMutabilityLevel; - static { - defaultBindings = new HashMap<>(); - defaultBindings.put("local", Name.LOCAL_NS); - defaultBindings.put("fn", Name.FN_NS); - defaultBindings.put("math", Name.MATH_NS); - defaultBindings.put("map", Name.MAP_NS); - defaultBindings.put("array", Name.ARRAY_NS); - defaultBindings.put("xs", Name.XS_NS); - defaultBindings.put("xsi", Name.XSI_NS); - defaultBindings.put("xml", Name.XML_NS); - defaultBindings.put("jn", Name.JN_NS); - defaultBindings.put("js", Name.JS_NS); - defaultBindings.put("err", Name.ERROR_NS); - defaultBindings.put("an", Name.JSONIQ_ANNOTATIONS_NS); - } - private RumbleRuntimeConfiguration configuration; - public StaticContext() { - this.parent = null; - this.staticBaseURI = null; - this.queryLanguage = null; - this.inScopeVariables = null; - this.userDefinedFunctionExecutionModes = null; - this.emptySequenceOrderLeast = true; - this.boundarySpacePreserve = true; - this.contextItemStaticType = null; - this.configuration = null; - this.inScopeSchemaTypes = null; - this.currentMutabilityLevel = 0; - this.serializationParameters = null; - this.explicitSerializationParameterNames = null; - this.defaultDecimalFormat = null; - this.decimalFormats = new HashMap<>(); - this.isQuerySideEffecting = false; - initializeRootCollations(); - } - public StaticContext(URI staticBaseURI, RumbleRuntimeConfiguration configuration) { - this.parent = null; this.staticBaseURI = staticBaseURI; - this.queryLanguage = configuration.getQueryLanguage() != null - ? configuration.getQueryLanguage() - : this.queryLanguage; this.configuration = configuration; - this.inScopeVariables = new HashMap<>(); - this.userDefinedFunctionExecutionModes = null; - this.emptySequenceOrderLeast = true; - this.boundarySpacePreserve = true; - this.contextItemStaticType = null; - this.staticallyKnownFunctionSignatures = new HashMap<>(); - this.inScopeSchemaTypes = new InScopeSchemaTypes(); - this.currentMutabilityLevel = 0; - this.serializationParameters = SerializationParameters.copy(configuration.getSerializationParameters()); - this.defaultDecimalFormat = DecimalFormatDefinition.defaultInstance(); - this.decimalFormats = new HashMap<>(); - this.isQuerySideEffecting = false; - initializeRootCollations(); + if (configuration != null) { + this.queryLanguage = configuration.getQueryLanguage(); + this.serializationParameters = SerializationParameters.copy(configuration.getSerializationParameters()); + } } + /** + * Initialize a child static context + * + * @param parent the parent static context + */ public StaticContext(StaticContext parent) { - this.parent = parent; - this.queryLanguage = null; - this.inScopeVariables = new HashMap<>(); - this.userDefinedFunctionExecutionModes = null; - this.contextItemStaticType = null; - this.staticallyKnownFunctionSignatures = new HashMap<>(); - this.configuration = null; - this.inScopeSchemaTypes = null; + this.parent = Objects.requireNonNull(parent, "parent"); this.currentMutabilityLevel = parent.currentMutabilityLevel; - this.serializationParameters = null; - this.defaultDecimalFormat = null; - this.decimalFormats = null; - this.isQuerySideEffecting = false; - this.staticallyKnownCollations = null; - this.defaultCollation = null; - } - - private void initializeRootCollations() { - this.staticallyKnownCollations = new LinkedHashSet<>(CollationCatalogue.defaultStaticallyKnownCollations()); - this.defaultCollation = CollationCatalogue.CODEPOINT_COLLATION; - } - - private void ensureRootCollationsInitialized() { - if (this.parent != null) { - this.parent.ensureRootCollationsInitialized(); - return; - } - if (this.staticallyKnownCollations == null) { - initializeRootCollations(); - } else if (this.defaultCollation == null) { - this.defaultCollation = CollationCatalogue.CODEPOINT_COLLATION; - } - } + this.inScopeSchemaTypes = parent.inScopeSchemaTypes; + this.serializationParameters = SerializationParameters.copy(parent.serializationParameters); - public StaticContext getParent() { - return this.parent; + // Local maps are initialized at declaration. + // Other fields inherit through parent lookup. } public RumbleRuntimeConfiguration getRumbleConfiguration() { @@ -182,10 +154,6 @@ public RumbleRuntimeConfiguration getRumbleConfiguration() { throw new OurBadException("Configuration not set."); } - public void setQueryLanguage(String queryLanguage) { - this.queryLanguage = queryLanguage; - } - public String getQueryLanguage() { if (this.queryLanguage != null) { return this.queryLanguage; @@ -211,56 +179,47 @@ public void setStaticBaseUri(URI staticBaseURI) { } public boolean isInScope(Name varName) { - boolean found = false; - if (this.inScopeVariables.containsKey(varName)) { - return true; - } else { - StaticContext ancestor = this.parent; - while (ancestor != null) { - found = found || ancestor.getInScopeVariables().containsKey(varName); - ancestor = ancestor.parent; + StaticContext context = this; + while (context != null) { + if (context.inScopeVariables.containsKey(varName)) { + return true; } + context = context.parent; } - return found; + return false; } private InScopeVariable getInScopeVariable(Name varName) { - if (this.inScopeVariables.containsKey(varName)) { - return this.inScopeVariables.get(varName); - } else { - StaticContext ancestor = this.parent; - while (ancestor != null) { - if (ancestor.inScopeVariables.containsKey(varName)) { - return ancestor.inScopeVariables.get(varName); - } - ancestor = ancestor.parent; + StaticContext context = this; + while (context != null) { + InScopeVariable variable = context.inScopeVariables.get(varName); + if (variable != null) { + return variable; } - throw new SemanticException("Variable " + varName + " not in scope", ExceptionMetadata.EMPTY_METADATA); + context = context.parent; } + throw new SemanticException("Variable " + varName + " not in scope", ExceptionMetadata.EMPTY_METADATA); } public FunctionSignature getFunctionSignature(FunctionIdentifier identifier) { - if (this.staticallyKnownFunctionSignatures.containsKey(identifier)) { - return this.staticallyKnownFunctionSignatures.get(identifier); - } else { - StaticContext ancestor = this.parent; - while (ancestor != null) { - if (ancestor.staticallyKnownFunctionSignatures.containsKey(identifier)) { - return ancestor.staticallyKnownFunctionSignatures.get(identifier); - } - ancestor = ancestor.parent; + StaticContext context = this; + while (context != null) { + FunctionSignature signature = context.staticallyKnownFunctionSignatures.get(identifier); + if (signature != null) { + return signature; } - throw new UnknownFunctionCallException( - identifier.getName(), - identifier.getArity(), - ExceptionMetadata.EMPTY_METADATA - ); + context = context.parent; } + throw new UnknownFunctionCallException( + identifier.getName(), + identifier.getArity(), + ExceptionMetadata.EMPTY_METADATA + ); } // replace the sequence type of an existing InScopeVariable, throws an error if the variable does not exists public void replaceVariableSequenceType(Name varName, SequenceType newSequenceType) { - InScopeVariable variable = getInScopeVariable(varName); + InScopeVariable variable = this.getInScopeVariable(varName); this.inScopeVariables.replace( varName, new InScopeVariable( @@ -274,19 +233,15 @@ public void replaceVariableSequenceType(Name varName, SequenceType newSequenceTy } public SequenceType getVariableSequenceType(Name varName) { - return getInScopeVariable(varName).getSequenceType(); - } - - public ExceptionMetadata getVariableMetadata(Name varName) { - return getInScopeVariable(varName).getMetadata(); + return this.getInScopeVariable(varName).getSequenceType(); } public ExecutionMode getVariableStorageMode(Name varName) { - return getInScopeVariable(varName).getStorageMode(); + return this.getInScopeVariable(varName).getStorageMode(); } public void setVariableStorageMode(Name varName, ExecutionMode mode) { - getInScopeVariable(varName).setStorageMode(mode); + this.getInScopeVariable(varName).setStorageMode(mode); } public void addVariable( @@ -316,14 +271,6 @@ public void addFunctionSignature(FunctionIdentifier identifier, FunctionSignatur this.staticallyKnownFunctionSignatures.put(identifier, signature); } - public Map getInScopeVariables() { - return this.inScopeVariables; - } - - public void show() { - System.err.println(this); - } - @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); @@ -347,44 +294,29 @@ public String toString() { stringBuilder.append(" (namespace " + entry.getKey().getName().getNamespace() + ")"); stringBuilder.append("\n"); } - if (this.inScopeSchemaTypes != null) { - stringBuilder.append("Static context with user-defined types:\n"); - for (ItemType itemType : this.inScopeSchemaTypes.getInScopeSchemaTypes()) { - stringBuilder.append(itemType.getName()); - stringBuilder.append(itemType.isResolved() ? " (resolved)" : " (unresolved)"); - stringBuilder.append("\n"); - } + stringBuilder.append("Static context with user-defined types:\n"); + for (ItemType itemType : this.inScopeSchemaTypes.getInScopeSchemaTypes()) { + stringBuilder.append(itemType.getName()); + stringBuilder.append(itemType.isResolved() ? " (resolved)" : " (unresolved)"); stringBuilder.append("\n"); } + stringBuilder.append("\n"); if (this.userDefinedFunctionExecutionModes != null) { - stringBuilder.append(this.userDefinedFunctionExecutionModes.toString()); + stringBuilder.append(this.userDefinedFunctionExecutionModes); } if (this.parent != null) { stringBuilder.append("\nParent:"); - stringBuilder.append(this.parent.toString()); + stringBuilder.append(this.parent); } return stringBuilder.toString(); } - public boolean hasVariable(Name variableName) { - if (this.inScopeVariables.containsKey(variableName)) { - return true; - } - if (this.parent != null) { - return this.parent.hasVariable(variableName); - } - return false; - } - public boolean hasVariableInScopeOnly(Name variableName) { return this.inScopeVariables.containsKey(variableName); } public boolean bindNamespace(String prefix, String namespace) { - if (this.staticallyKnownNamespaces == null) { - this.staticallyKnownNamespaces = new HashMap<>(); - } - if (canBindNamespace(prefix)) { + if (this.canBindNamespace(prefix)) { this.staticallyKnownNamespaces.put(prefix, namespace); return true; } @@ -395,10 +327,7 @@ public boolean bindNamespace(String prefix, String namespace) { * Explicitly removes a namespace binding in this context, shadowing any inherited or predeclared binding. */ public boolean unbindNamespace(String prefix) { - if (this.staticallyKnownNamespaces == null) { - this.staticallyKnownNamespaces = new HashMap<>(); - } - if (!canBindNamespace(prefix)) { + if (!this.canBindNamespace(prefix)) { return false; } this.staticallyKnownNamespaces.put(prefix, null); @@ -409,12 +338,12 @@ private boolean canBindNamespace(String prefix) { if (!this.staticallyKnownNamespaces.containsKey(prefix)) { return true; } - return defaultBindings.containsKey(prefix) - && defaultBindings.get(prefix).equals(this.staticallyKnownNamespaces.get(prefix)); + return DEFAULT_BINDINGS.containsKey(prefix) + && DEFAULT_BINDINGS.get(prefix).equals(this.staticallyKnownNamespaces.get(prefix)); } public String resolveNamespace(String prefix) { - if (this.staticallyKnownNamespaces != null && this.staticallyKnownNamespaces.containsKey(prefix)) { + if (this.staticallyKnownNamespaces.containsKey(prefix)) { return this.staticallyKnownNamespaces.get(prefix); } if (this.parent != null) { @@ -428,84 +357,30 @@ public Map getInScopeNamespaceBindings() { if (this.parent != null) { bindings.putAll(this.parent.getInScopeNamespaceBindings()); } - if (this.staticallyKnownNamespaces != null) { - bindings.putAll(this.staticallyKnownNamespaces); - } + bindings.putAll(this.staticallyKnownNamespaces); return bindings; } - /** - * Returns the default serialization parameters stored in the static context. - * - * Spec references: - * - *
    - *
  • XQuery 3.1 Static Context Components (link: - * https://www.w3.org/TR/xquery-31/#id-xq-static-context-components)
  • - *
  • Serialization 3.1 — Serialization Parameters (link: - * https://www.w3.org/TR/xslt-xquery-serialization-31/#serparam)
  • - *
- */ - public SerializationParameters getSerializationParameters() { - if (this.serializationParameters != null) { - return this.serializationParameters; - } - // Backward compatibility: if absent locally (e.g., contexts deserialized from older versions), - // delegate to parent to preserve inheritance instead of creating a shadow copy here. - if (this.parent != null) { - return this.parent.getSerializationParameters(); - } - // Root context missing the field (e.g., deserialized from an older version): populate defaults once. - this.serializationParameters = SerializationParameters.defaults(); - return this.serializationParameters; - } - - /** - * Sets the default serialization parameters at this static context level. - */ - public void setSerializationParameters(SerializationParameters serializationParameters) { - this.serializationParameters = serializationParameters; - } - - /** - * Override the serialization parameters with the provided parameter name and value. - * Throws InvalidSerializationParameterValueException for invalid inputs. - * - * @param name the name of the parameter to update - * @param value the value of the parameter to update - * @throws org.rumbledb.exceptions.InvalidSerializationParameterValueException if the parameter value is invalid - */ - public void overrideSerializationParameter(String name, String value) { - overrideSerializationParameter(name, value, ExceptionMetadata.EMPTY_METADATA); - } - public void overrideSerializationParameter(String name, String value, ExceptionMetadata metadata) { - // ensure we have a local copy of the serialization parameters - if (this.serializationParameters == null) { - this.serializationParameters = SerializationParameters.copy(this.getSerializationParameters()); - } if ("parameter-document".equals(name)) { SerializationParameterUtils.applyParameterDocument( this.serializationParameters, this, value, - getExplicitSerializationParameterNames(), + this.getExplicitSerializationParameterNames(), metadata ); return; } if ("cdata-section-elements".equals(name) || "suppress-indentation".equals(name)) { - value = expandSerializationQNames(value); + value = this.expandSerializationQNames(value); } - getExplicitSerializationParameterNames().add(name); + this.getExplicitSerializationParameterNames().add(name); // update the local copy of theserialization parameters with the provided parameter name and value SerializationParameterBuilder.update(this.serializationParameters, name, value); } private Set getExplicitSerializationParameterNames() { - if (this.explicitSerializationParameterNames == null) { - this.explicitSerializationParameterNames = new LinkedHashSet<>(); - } return this.explicitSerializationParameterNames; } @@ -519,7 +394,7 @@ private String expandSerializationQNames(String value) { if (token.isEmpty()) { continue; } - sb.append(separator).append(expandSerializationQName(token)); + sb.append(separator).append(this.expandSerializationQName(token)); separator = " "; } return sb.toString(); @@ -531,7 +406,7 @@ private String expandSerializationQName(String lexicalQName) { } int colon = lexicalQName.indexOf(':'); if (colon < 0) { - String namespace = getInScopeNamespaceBindings().get(""); + String namespace = this.getInScopeNamespaceBindings().get(""); if (namespace == null || namespace.isEmpty()) { return lexicalQName; } @@ -539,7 +414,7 @@ private String expandSerializationQName(String lexicalQName) { } String prefix = lexicalQName.substring(0, colon); String localName = lexicalQName.substring(colon + 1); - String namespace = getInScopeNamespaceBindings().get(prefix); + String namespace = this.getInScopeNamespaceBindings().get(prefix); if (namespace == null) { return lexicalQName; } @@ -547,14 +422,8 @@ private String expandSerializationQName(String lexicalQName) { } public void importModuleContext(StaticContext moduleContext) { - for (Name name : moduleContext.inScopeVariables.keySet()) { - InScopeVariable variable = moduleContext.inScopeVariables.get(name); - this.inScopeVariables.put(name, variable); - } - for (FunctionIdentifier fi : moduleContext.staticallyKnownFunctionSignatures.keySet()) { - FunctionSignature signature = moduleContext.staticallyKnownFunctionSignatures.get(fi); - this.staticallyKnownFunctionSignatures.put(fi, signature); - } + this.inScopeVariables.putAll(moduleContext.inScopeVariables); + this.staticallyKnownFunctionSignatures.putAll(moduleContext.staticallyKnownFunctionSignatures); } public void setUserDefinedFunctionsExecutionModes( @@ -624,23 +493,14 @@ public boolean isBoundarySpacePreserve() { return this.boundarySpacePreserve; } - public void addStaticallyKnownCollation(String uri) { - if (this.parent != null) { - throw new OurBadException("Statically known collations can only be set in the root static context."); - } - ensureRootCollationsInitialized(); - this.staticallyKnownCollations.add(uri); - } - public boolean isStaticallyKnownCollation(String uri) { - return getStaticallyKnownCollations().contains(uri); + return this.getStaticallyKnownCollations().contains(uri); } public Set getStaticallyKnownCollations() { if (this.parent != null) { return this.parent.getStaticallyKnownCollations(); } - ensureRootCollationsInitialized(); return Collections.unmodifiableSet(this.staticallyKnownCollations); } @@ -648,7 +508,6 @@ public void setDefaultCollation(String uri) { if (this.parent != null) { throw new OurBadException("Default collation can only be set in the root static context."); } - ensureRootCollationsInitialized(); if (!this.staticallyKnownCollations.contains(uri)) { throw new OurBadException("Default collation must be statically known."); } @@ -659,25 +518,9 @@ public String getDefaultCollation() { if (this.parent != null) { return this.parent.getDefaultCollation(); } - ensureRootCollationsInitialized(); return this.defaultCollation; } - public StaticContext getModuleContext() { - if (this.parent != null) { - return this.parent.getModuleContext(); - } - return this; - } - - public SequenceType getContextItemStaticType() { - return this.contextItemStaticType; - } - - public void setContextItemStaticType(SequenceType contextItemStaticType) { - this.contextItemStaticType = contextItemStaticType; - } - // replace all inScopeVariable in this context and all parents until [stopContext] with name not in [varToExclude] // with same variable with sequence type arity changed from 1 to + and form ? to * // used by groupBy clause @@ -687,7 +530,7 @@ public void incrementArities(StaticContext stopContext, Set varToExclude) ? value : new InScopeVariable( value.getName(), - incrementArity(value.getSequenceType()), + this.incrementArity(value.getSequenceType()), value.getMetadata(), value.getStorageMode() ) @@ -700,7 +543,7 @@ public void incrementArities(StaticContext stopContext, Set varToExclude) entry.getKey(), varToExclude.contains(entry.getKey()) ? entry.getValue().getSequenceType() - : incrementArity(entry.getValue().getSequenceType()), + : this.incrementArity(entry.getValue().getSequenceType()), entry.getValue().getMetadata(), entry.getValue().isAssignable() ); @@ -717,8 +560,8 @@ private SequenceType incrementArity(SequenceType sequenceType) { } public void bindDefaultNamespaces() { - for (String prefix : defaultBindings.keySet()) { - bindNamespace(prefix, defaultBindings.get(prefix)); + for (Map.Entry binding : DEFAULT_BINDINGS.entrySet()) { + this.bindNamespace(binding.getKey(), binding.getValue()); } } @@ -726,25 +569,7 @@ public void bindDefaultNamespaces() { * Built-in namespace bindings (fn, xs, map, ...) used when resolving QNames without a full static context. */ public static String getBuiltinNamespaceBinding(String prefix) { - return defaultBindings.get(prefix); - } - - public InScopeSchemaTypes getInScopeSchemaTypes() { - if (this.inScopeSchemaTypes != null) { - return this.inScopeSchemaTypes; - } - if (this.parent != null) { - return this.parent.getInScopeSchemaTypes(); - } - throw new OurBadException("In-scope schema types are not set up properly in static context."); - } - - public int getCurrentMutabilityLevel() { - return this.currentMutabilityLevel; - } - - public void setCurrentMutabilityLevel(int currentMutabilityLevel) { - this.currentMutabilityLevel = currentMutabilityLevel; + return DEFAULT_BINDINGS.get(prefix); } public boolean getIsAssignable(Name name) { @@ -784,9 +609,6 @@ public Map getDecimalFormats() { if (this.parent != null) { return this.parent.getDecimalFormats(); } - if (this.decimalFormats == null) { - return Collections.emptyMap(); - } return Collections.unmodifiableMap(this.decimalFormats); }