src/Proton/Types/Symbol.cs (144 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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. */ using Apache.Qpid.Proton.Buffer; using System; using System.Text; using System.Collections.Concurrent; namespace Apache.Qpid.Proton.Types { public sealed class Symbol : IEquatable<Symbol>, IComparable, IComparable<Symbol> { private static readonly ConcurrentDictionary<IProtonBuffer, Symbol> buffersToSymbols = new(); private static readonly ConcurrentDictionary<string, Symbol> stringsToSymbols = new(); private static readonly Symbol EMPTY_SYMBOL = new(); // Prevents the Symbol cache from growing overly large if abused by creating overly // large Symbols which would all be stored in the symbol cache. private static readonly uint MAX_CACHED_SYMBOL_SIZE = 64; // Lazy allocated based on calls to stringify the given Symbol private string symbolString; private readonly IProtonBuffer underlying; private readonly int hashCode; private Symbol() { underlying = ProtonByteBufferAllocator.Instance.Allocate(0, 0); symbolString = ""; hashCode = 32; } private Symbol(IProtonBuffer buffer) { underlying = buffer; hashCode = buffer.GetHashCode(); } /// <summary> /// Allows a string value to be implicitly converted to a Symbol /// </summary> /// <param name="symbolString">The String to convert</param> public static implicit operator Symbol(string symbolString) => Lookup(symbolString); /// <summary> /// Allows a Symbol object to be implicitly converted to a string value. /// </summary> /// <param name="value">The Symbol to convert</param> public static implicit operator string(Symbol value) => value?.ToString(); /// <summary> /// Lookup or create a singleton instance of the given Symbol that has the /// matching name to the string value provided. /// </summary> /// <param name="value">the stringified symbol name</param> /// <returns>A singleton instance of the named Symbol</returns> public static Symbol Lookup(string value) { if (value == null) { return null; } else if (value.Length == 0) { return EMPTY_SYMBOL; } else { if (!stringsToSymbols.TryGetValue(value, out Symbol symbol)) { symbol = Lookup(ProtonByteBufferAllocator.Instance.Wrap(Encoding.ASCII.GetBytes(value))); if (symbol.Length <= MAX_CACHED_SYMBOL_SIZE) { // Try and keep the Symbol instance consistent with the one that is stored // in the buffer to symbol dictionary. stringsToSymbols[value] = symbol; } } return symbol; } } /// <summary> /// Lookup or create a singleton instance of the given Symbol that has the /// matching byte contents as the given buffer, if none exists a new Symbol /// is created using the given buffer which is not copied but used directly. /// </summary> /// <param name="value">the stringified symbol name</param> /// <returns>A singleton instance of the named Symbol</returns> public static Symbol Lookup(IProtonBuffer value) { return Lookup(value, false); } /// <summary> /// Lookup or create a singleton instance of the given Symbol that has the /// matching byte contents as the given buffer, if none exists a new Symbol /// is created using the given buffer which is not copied if the provided /// boolean option requests it. /// </summary> /// <param name="value">the stringified symbol name</param> /// <param name="copyOnCreate">should the given buffer be copied if a Symbol is created</param> /// <returns>A singleton instance of the named Symbol</returns> public static Symbol Lookup(IProtonBuffer value, bool copyOnCreate) { if (value == null) { return null; } else if (!value.IsReadable) { return EMPTY_SYMBOL; } else { if (!buffersToSymbols.TryGetValue(value, out Symbol symbol)) { if (copyOnCreate) { long symbolSize = value.ReadableBytes; IProtonBuffer copy = ProtonByteBufferAllocator.Instance.Allocate(symbolSize, symbolSize); value.CopyInto(value.ReadOffset, copy, 0, symbolSize); copy.WriteOffset = symbolSize; value = copy; } symbol = new Symbol(value); if (symbol.Length <= MAX_CACHED_SYMBOL_SIZE) { if (!buffersToSymbols.TryAdd(value, symbol)) { symbol = buffersToSymbols[value]; } } } return symbol; } } /// <summary> /// Returns the number of ASCII characters that comprise this Symbol /// </summary> public int Length { get { return (int)underlying.ReadableBytes; } } /// <summary> /// Writes a copy of the Symbol bytes to the given buffer. /// </summary> /// <param name="buffer">The buffer to write the Symbol bytes to</param> public void WriteTo(IProtonBuffer buffer) { buffer.EnsureWritable(Length); underlying.CopyInto(underlying.ReadOffset, buffer, buffer.WriteOffset, underlying.ReadableBytes); buffer.WriteOffset += Length; } public override string ToString() { if (symbolString == null && underlying.IsReadable) { symbolString = underlying.ToString(Encoding.ASCII); if (symbolString.Length <= MAX_CACHED_SYMBOL_SIZE) { if (!stringsToSymbols.TryAdd(symbolString, this)) { symbolString = stringsToSymbols[symbolString].symbolString; } } } return symbolString ?? ""; } public override int GetHashCode() { return hashCode; } public override bool Equals(object symbol) { if (symbol == null || symbol.GetType() != GetType()) { return false; } return Equals(symbol as Symbol); } public bool Equals(Symbol symbol) { if (symbol == null) { return false; } return underlying.Equals(symbol.underlying); } public int CompareTo(Symbol other) { return underlying.CompareTo(other.underlying); } public int CompareTo(object other) { return CompareTo(other as Symbol); } } }