content: // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  // SPDX-License-Identifier: Apache-2.0
  package com.amazon.ion.impl.lite;
  
  import static com.amazon.ion.SymbolTable.UNKNOWN_SYMBOL_ID;
  import static com.amazon.ion.SystemSymbols.ION_1_0;
  import static com.amazon.ion.SystemSymbols.ION_1_0_SID;
  
  import com.amazon.ion.IonType;
  import com.amazon.ion.IonWriter;
  import com.amazon.ion.NullValueException;
  import com.amazon.ion.SymbolTable;
  import com.amazon.ion.SymbolToken;
  import com.amazon.ion.UnknownSymbolException;
  import com.amazon.ion.ValueVisitor;
  import com.amazon.ion.impl._Private_IonSymbol;
  import com.amazon.ion.impl._Private_Utils;
  import java.io.IOException;
  
  
  final class IonSymbolLite
      extends IonTextLite
      implements _Private_IonSymbol
  {
      private static final int HASH_SIGNATURE =
          IonType.SYMBOL.toString().hashCode();
  
      private int _sid = UNKNOWN_SYMBOL_ID;
  
      /**
       * @param isNull if {@code true}, constructs a {@code null.symbol} value.
       */
      IonSymbolLite(ContainerlessContext context, boolean isNull)
      {
          super(context, isNull);
      }
  
      IonSymbolLite(IonSymbolLite existing, IonContext context) throws UnknownSymbolException
      {
          super(existing, context);
      }
  
      IonSymbolLite(ContainerlessContext context, SymbolToken sym)
      {
          super(context, sym == null);
          if (sym != null)
          {
              String text = sym.getText();
              int sid = sym.getSid();
              assert text != null || sid >= 0;
  
              if (text != null)
              {
                  super.setValue(text);
                  // TODO [amazon-ion/ion-java/issues/27] - needs consistent handling, when to retain SID's vs ignore
              }
              else
              {
                  // TODO [amazon-ion/ion-java/issues/223] - needs consistent handling, resolution against context symbol table
                  _sid = sid;
                  // there *is* an encoding present so we must update
                  _isSymbolIdPresent(true);
              }
          }
      }
  
      @Override
      IonValueLite shallowClone(IonContext context)
      {
          IonSymbolLite clone = new IonSymbolLite(this, context);
          if(this._sid == 0) {
              clone._sid = 0;
          }
          return clone;
      }
  
      @Override
      public IonSymbolLite clone() throws UnknownSymbolException
      {
          // If this symbol has unknown text but known Sid, this symbol has no
          // semantic meaning, as such cloning should throw an exception.
          if (!isNullValue()
              && _sid != UNKNOWN_SYMBOL_ID && _sid != 0
              && _stringValue() == null) {
              throw new UnknownSymbolException(_sid);
          }
          return (IonSymbolLite) shallowClone(ContainerlessContext.wrap(getSystem()));
      }
  
      @Override
      int hashSignature() {
          return HASH_SIGNATURE;
      }
  
      @Override
      int scalarHashCode()
      {
          final int sidHashSalt   = 127;      // prime to salt sid
          final int textHashSalt  = 31;       // prime to salt text
          int result = HASH_SIGNATURE;
          int tokenHashCode = _text_value == null
              ? _sid  * sidHashSalt
              : _text_value.hashCode() * textHashSalt;
  
          // mixing to account for small text and sid deltas
          tokenHashCode ^= (tokenHashCode << 29) ^ (tokenHashCode >> 3);
  
          result ^= tokenHashCode;
  
          return hashTypeAnnotations(result);
      }
  
      @Override
      public IonType getTypeSlow()
      {
          return IonType.SYMBOL;
      }
  
      @Deprecated
      public int getSymbolId()
          throws NullValueException
      {
          return getSymbolId(null);
      }
  
      private int getSymbolId(SymbolTableProvider symbolTableProvider)
          throws NullValueException
      {
          validateThisNotNull();
  
          if (_sid != UNKNOWN_SYMBOL_ID || isReadOnly()) {
              return _sid;
          }
  
          SymbolTable symtab =
                  symbolTableProvider != null ? symbolTableProvider.getSymbolTable()
                                              : getSymbolTable();
          if (symtab == null) {
              symtab = getSystem().getSystemSymbolTable();
          }
          assert(symtab != null);
  
          String name = _get_value();
          // TODO [amazon-ion/ion-java/issues/27] - needs consistent handling, when to retain SID's vs ignore (here memoizing SID on read)
          if (!symtab.isLocalTable())
          {
              setSID(symtab.findSymbol(name));
              if (_sid > 0 || isReadOnly()) {
                  return _sid;
              }
          }
          SymbolToken tok = symtab.find(name);
          if (tok != null)
          {
              setSID(tok.getSid());
              _set_value(tok.getText()); // Use the interned instance of the text
          }
          return _sid;
      }
  
      private void setSID(int sid)
      {
          _sid = sid;
          if (_sid > 0)
          {
              cascadeSIDPresentToContextRoot();
          }
      }
  
  
      /**
       * Get's the text of this NON-NULL symbol, finding it from our symbol
       * table if it's not yet known (and caching the result if possible).
       * <p>
       * Caller must check {@link #isNullValue()}
       *
       * @return null if symbol text is unknown.
       */
      private String _stringValue()
      {
          return _stringValue(new LazySymbolTableProvider(this));
      }
  
      private String _stringValue(SymbolTableProvider symbolTableProvider)
      {
          String name = _get_value();
          if (name == null)
          {
              assert _sid >= 0;
  
              SymbolTable symbols = symbolTableProvider.getSymbolTable();
              name = symbols.findKnownSymbol(_sid);
              if (name != null)
              {
                  // if this is a mutable value we'll hang onto
                  // our now known symbol table so we don't have
                  // to look it up again.
                  // If the value is immutable, honor that contract.
                  if (_isLocked() == false) {
                      _set_value(name);
                  }
              }
          }
          return name;
      }
  
      public SymbolToken symbolValue()
      {
          return symbolValue(new LazySymbolTableProvider(this));
      }
  
      public SymbolToken symbolValue(SymbolTableProvider symbolTableProvider)
      {
          if (isNullValue()) return null;
  
          int sid = getSymbolId(symbolTableProvider);
          String text = _stringValue(symbolTableProvider);
          return _Private_Utils.newSymbolToken(text, sid);
      }
  
  
      @Override
      public void setValue(String value)
      {
          super.setValue(value);  // Calls checkForLock and _isNullValue
          _sid = UNKNOWN_SYMBOL_ID;
      }
  
      protected boolean isIonVersionMarker() {
          return _isIVM();
      }
  
      @Override
      boolean attemptClearSymbolIDValues()
      {
          boolean allSymbolIDsCleared = super.attemptClearSymbolIDValues();
  
          // if there is no value, or there is already no SID - there is no clear action
          if (isNullValue() || _sid == UNKNOWN_SYMBOL_ID)
          {
              // no behavior required - value has no SID value to clear
          }
          // if there is text, clear the SID
          else if (_stringValue() != null)
          {
              _sid = UNKNOWN_SYMBOL_ID;
          }
          else
          {
              // TODO [amazon-ion/ion-java/issues/223] - needs consistent handling, resolution against context symbol table
              // there is not text, so we can't clear the SID.
              allSymbolIDsCleared = false;
          }
  
          return allSymbolIDsCleared;
      }
  
      protected void setIsIonVersionMarker(boolean isIVM)
      {
          assert (ION_1_0.equals(this._get_value()) == isIVM);
  
          //_is_IonVersionMarker = isIVM;
          _isIVM(isIVM);
          _isSystemValue(isIVM);
  
          _sid = ION_1_0_SID;
      }
  
  
      @Override
      final void writeBodyTo(IonWriter writer, SymbolTableProvider symbolTableProvider)
          throws IOException
      {
          // TODO amazon-ion/ion-java/issues/27 Fix symbol handling
          // A million-dollar question is - if text is missing, do
          // we throw (cannot serialize) or do we pass the sid thru???
  
          // NB! This will throw if symbol is not set
          String text = _stringValue(symbolTableProvider);
          if (text != null) {
              writer.writeSymbol(text);
          } else {
              writer.writeSymbolToken(_Private_Utils.newSymbolToken(getSymbolId(symbolTableProvider)));
          }
  
      }
  
      @Override
      public String stringValue()
          throws UnknownSymbolException
      {
          return stringValue(new LazySymbolTableProvider(this));
      }
  
      private String stringValue(SymbolTableProvider symbolTableProvider)
          throws UnknownSymbolException
      {
          if (isNullValue()) {
              return null;
          }
          String name = _stringValue(symbolTableProvider);
          if (name == null) {
              assert(_sid >= 0);
              throw new UnknownSymbolException(_sid);
          }
          return name;
      }
  
      @Override
      public void accept(ValueVisitor visitor) throws Exception
      {
          visitor.visit(this);
      }
  
  }
  