net.sf.saxon.ma.map.DictionaryMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2020 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.ma.map;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.om.SequenceTool;
import net.sf.saxon.transpile.CSharpSuppressWarnings;
import net.sf.saxon.tree.iter.AtomicIterator;
import net.sf.saxon.type.*;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.StringValue;
import java.util.*;
/**
* A simple implementation of MapItem where the strings are keys, and modification is unlikely.
* This implementation is used in a number of cases where it can be determined that it is suitable,
* for example when parsing JSON input, or when creating a fixed map to use in an options argument.
*/
public class DictionaryMap extends MapItem {
private final Map hashMap;
/**
* Create an empty dictionary, to which entries can be added using {@link #initialPut(String, GroundedValue)},
* provided this is done before the map is exposed to the outside world.
*/
public DictionaryMap() {
hashMap = new HashMap<>();
}
/**
* Create an empty dictionary, to which entries can be added using {@link #initialPut(String, GroundedValue)},
* provided this is done before the map is exposed to the outside world.
* @param size estimated final size of the dictionary
*/
public DictionaryMap(int size) {
hashMap = new HashMap<>(size);
}
/**
* During initial construction of the map, add a key-value pair
* @param key the key
* @param value the corresponding value
*/
public void initialPut(String key, GroundedValue value) {
hashMap.put(key, value);
}
/**
* During initial construction of the map, append a value to a possibly existing key-value pair
* @param key the key
* @param value the corresponding value
*/
public void initialAppend(String key, GroundedValue value) {
GroundedValue existingValue = hashMap.get(key);
if (existingValue == null) {
initialPut(key, value);
} else {
hashMap.put(key, existingValue.concatenate(value));
}
}
/**
* Get an entry from the Map
*
* @param key the value of the key
* @return the value associated with the given key, or null if the key is not present in the map.
*/
@Override
public GroundedValue get(AtomicValue key) {
if (key instanceof StringValue) {
return hashMap.get(key.getStringValue());
} else {
return null;
}
}
/**
* Get the size of the map
*
* @return the number of keys/entries present in this map
*/
@Override
public int size() {
return hashMap.size();
}
/**
* Ask whether the map is empty
*
* @return true if and only if the size of the map is zero
*/
@Override
public boolean isEmpty() {
return hashMap.isEmpty();
}
/**
* Get the set of all key values in the map.
*
* @return a set containing all the key values present in the map, in unpredictable order
*/
@Override
public AtomicIterator keys() {
return new KeyIterator(hashMap);
}
/**
* Get the set of all key-value pairs in the map
*
* @return an iterable containing all the key-value pairs
*/
@Override
public Iterable keyValuePairs() {
List pairs = new ArrayList<>();
for (Map.Entry entry : hashMap.entrySet()) {
pairs.add(new KeyValuePair(new StringValue(entry.getKey()), entry.getValue()));
}
return pairs;
}
/**
* Create a new map containing the existing entries in the map plus an additional entry,
* without modifying the original. If there is already an entry with the specified key,
* this entry is replaced by the new entry.
*
* @param key the key of the new entry
* @param value the value associated with the new entry
* @return the new map containing the additional entry
*/
@Override
public MapItem addEntry(AtomicValue key, GroundedValue value) {
return toHashTrieMap().addEntry(key, value);
}
/**
* Remove an entry from the map
*
* @param key the key of the entry to be removed
* @return a new map in which the requested entry has been removed; or this map
* unchanged if the specified key was not present
*/
@Override
public MapItem remove(AtomicValue key) {
return toHashTrieMap().remove(key);
}
/**
* Ask whether the map conforms to a given map type
*
* @param keyType the required keyType
* @param valueType the required valueType
* @param th the type hierarchy cache for the configuration
* @return true if the map conforms to the required type
*/
@Override
public boolean conforms(AtomicType keyType, SequenceType valueType, TypeHierarchy th) {
if (isEmpty()) {
return true;
}
if (!(keyType == BuiltInAtomicType.STRING || keyType == BuiltInAtomicType.ANY_ATOMIC)) {
return false;
}
if (valueType.equals(SequenceType.ANY_SEQUENCE)) {
return true;
}
for (GroundedValue val : hashMap.values()) {
if (!valueType.matches(val, th)) {
return false;
}
}
return true;
}
/**
* Get the type of the map. This method is used largely for diagnostics, to report
* the type of a map when it differs from the required type.
*
* @param th the type hierarchy cache
* @return the type of this map
*/
@Override
public ItemType getItemType(TypeHierarchy th) {
ItemType valueType = null;
int valueCard = 0;
// we need to test the entries individually
AtomicIterator keyIter = keys();
AtomicValue key;
for (Map.Entry entry : hashMap.entrySet()) {
GroundedValue val = entry.getValue();
if (valueType == null) {
valueType = SequenceTool.getItemType(val, th);
valueCard = SequenceTool.getCardinality(val);
} else {
valueType = Type.getCommonSuperType(valueType, SequenceTool.getItemType(val, th), th);
valueCard = Cardinality.union(valueCard, SequenceTool.getCardinality(val));
}
}
if (valueType == null) {
// empty map
return MapType.EMPTY_MAP_TYPE;
} else {
return new MapType(BuiltInAtomicType.STRING, SequenceType.makeSequenceType(valueType, valueCard));
}
}
/**
* Get the lowest common item type of the keys in the map
*
* @return the most specific type to which all the keys belong. If the map is
* empty, return UType.VOID
*/
@Override
public UType getKeyUType() {
return hashMap.isEmpty() ? UType.VOID : UType.STRING;
}
/**
* Convert to a HashTrieMap
*/
private HashTrieMap toHashTrieMap() {
//System.err.println("Dictionary rewrite!!!!");
HashTrieMap target = new HashTrieMap();
for (Map.Entry entry : hashMap.entrySet()) {
target.initialPut(new StringValue(entry.getKey()), entry.getValue());
}
return target;
}
private class KeyIterator implements AtomicIterator {
Iterator keyIter;
public KeyIterator(Map hashMap) {
this.keyIter = hashMap.keySet().iterator();
}
@CSharpSuppressWarnings("UnsafeIteratorConversion")
public AtomicValue next() {
if (this.keyIter.hasNext()) {
return new StringValue(this.keyIter.next());
} else {
return null;
}
}
}
}