code: /*
   * Copyright (c) 2021 the Eclipse Milo Authors
   *
   * This program and the accompanying materials are made
   * available under the terms of the Eclipse Public License 2.0
   * which is available at https://www.eclipse.org/legal/epl-2.0/
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  
  package org.eclipse.milo.opcua.sdk.client.nodes;
  
  import java.util.concurrent.CompletableFuture;
  
  import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
  import org.eclipse.milo.opcua.sdk.core.nodes.VariableTypeNode;
  import org.eclipse.milo.opcua.sdk.core.nodes.VariableTypeNodeProperties;
  import org.eclipse.milo.opcua.stack.core.AttributeId;
  import org.eclipse.milo.opcua.stack.core.StatusCodes;
  import org.eclipse.milo.opcua.stack.core.UaException;
  import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
  import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
  import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
  import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
  import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
  import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
  import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
  import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
  import org.jetbrains.annotations.Nullable;
  
  public class UaVariableTypeNode extends UaNode implements VariableTypeNode {
  
      private DataValue value;
      private NodeId dataType;
      private Integer valueRank;
      private UInteger[] arrayDimensions;
      private Boolean isAbstract;
  
      public UaVariableTypeNode(
          OpcUaClient client,
          NodeId nodeId,
          NodeClass nodeClass,
          QualifiedName browseName,
          LocalizedText displayName,
          LocalizedText description,
          UInteger writeMask,
          UInteger userWriteMask,
          DataValue value,
          NodeId dataType,
          Integer valueRank,
          UInteger[] arrayDimensions,
          Boolean isAbstract
      ) {
  
          super(client, nodeId, nodeClass, browseName, displayName, description, writeMask, userWriteMask);
  
          this.value = value;
          this.dataType = dataType;
          this.valueRank = valueRank;
          this.arrayDimensions = arrayDimensions;
          this.isAbstract = isAbstract;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The returned attribute is the most recently seen value; it is not read live from the server.
       *
       * @see #readValue()
       */
      @Override
      public synchronized DataValue getValue() {
          return value;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The returned attribute is the most recently seen value; it is not read live from the server.
       *
       * @see #readDataType()
       */
      @Override
      public synchronized NodeId getDataType() {
          return dataType;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The returned attribute is the most recently seen value; it is not read live from the server.
       *
       * @see #readValueRank()
       */
      @Override
      public synchronized Integer getValueRank() {
          return valueRank;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The returned attribute is the most recently seen value; it is not read live from the server.
       *
       * @see #readArrayDimensions()
       */
      @Override
      public synchronized UInteger[] getArrayDimensions() {
          return arrayDimensions;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The returned attribute is the most recently seen value; it is not read live from the server.
       *
       * @see #readIsAbstract()
       */
      @Override
      public synchronized Boolean getIsAbstract() {
          return this.isAbstract;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The attribute is only updated locally; it is not written to the server.
       *
       * @see #writeValue(DataValue)
       */
      @Override
      public synchronized void setValue(DataValue value) {
          this.value = value;
      }
  
      /**
       * Set the local Value attribute to a {@link DataValue} containing {@code variant} with no
       * StatusCode or timestamp values.
       *
       * @param variant the {@link Variant} to set.
       * @see #setValue(DataValue)
       */
      public synchronized void setValue(Variant variant) {
          setValue(DataValue.valueOnly(variant));
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The attribute is only updated locally; it is not written to the server.
       *
       * @see #writeDataType(NodeId)
       */
      @Override
      public synchronized void setDataType(NodeId dataType) {
          this.dataType = dataType;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The attribute is only updated locally; it is not written to the server.
       *
       * @see #writeValueRank(Integer)
       */
      @Override
      public synchronized void setValueRank(Integer valueRank) {
          this.valueRank = valueRank;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The attribute is only updated locally; it is not written to the server.
       *
       * @see #writeArrayDimensions(UInteger[])
       */
      @Override
      public synchronized void setArrayDimensions(UInteger[] arrayDimensions) {
          this.arrayDimensions = arrayDimensions;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The attribute is only updated locally; it is not written to the server.
       *
       * @see #writeIsAbstract(Boolean)
       */
      @Override
      public synchronized void setIsAbstract(Boolean isAbstract) {
          this.isAbstract = isAbstract;
      }
  
      /**
       * Read the Value attribute for this Node from the server and update the local attribute.
       * <p>
       * Unlike reads for other attributes this call does not fail if the resulting {@link DataValue}
       * is not good quality and the local attribute value is updated without regard for status.
       *
       * @return the {@link DataValue} read from the server.
       * @throws UaException if a service-level error occurs.
       */
      public DataValue readValue() throws UaException {
          DataValue value = readAttribute(AttributeId.Value);
          setValue(value);
          return value;
      }
  
      /**
       * Read the DataType attribute for this Node from the server and update the local attribute if
       * the operation succeeds.
       *
       * @return the {@link NodeId} read from the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public NodeId readDataType() throws UaException {
          DataValue value = readAttribute(AttributeId.DataType);
  
          StatusCode statusCode = value.getStatusCode();
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "read DataType failed");
          } else {
              NodeId dataType = (NodeId) value.getValue().getValue();
              setDataType(dataType);
              return dataType;
          }
      }
  
      /**
       * Read the ValueRank attribute for this Node from the server and update the local attribute if
       * the operation succeeds.
       *
       * @return the {@link Integer} read from the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public Integer readValueRank() throws UaException {
          DataValue value = readAttribute(AttributeId.ValueRank);
  
          StatusCode statusCode = value.getStatusCode();
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "read ValueRank failed");
          } else {
              Integer valueRank = (Integer) value.getValue().getValue();
              setValueRank(valueRank);
              return valueRank;
          }
      }
  
      /**
       * Read the ArrayDimensions attribute for this Node from the server and update the local
       * attribute if the operation succeeds.
       * <p>
       * ArrayDimensions is an optional attribute. If the operation fails with
       * {@link StatusCodes#Bad_AttributeIdInvalid} a {@code null} value is returned instead.
       * <p>
       * Note that {@code null} is an expected value for the ArrayDimensions attribute in some cases
       * and this call does not distinguish between the attribute existing with a null value and the
       * attribute not existing.
       *
       * @return the {@link UInteger} array read from the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      @Nullable
      public UInteger[] readArrayDimensions() throws UaException {
          DataValue value = readAttribute(AttributeId.ArrayDimensions);
          StatusCode statusCode = value.getStatusCode();
  
          if (statusCode != null &&
              statusCode.isBad() &&
              statusCode.getValue() != StatusCodes.Bad_AttributeIdInvalid) {
  
              throw new UaException(statusCode, "read ArrayDimensions failed");
          } else {
              UInteger[] arrayDimensions = (UInteger[]) value.getValue().getValue();
              setArrayDimensions(arrayDimensions);
              return arrayDimensions;
          }
      }
  
      /**
       * Read the IsAbstract attribute for this Node from the server and update the local attribute
       * if the operation succeeds.
       *
       * @return the {@link Boolean} read from the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public Boolean readIsAbstract() throws UaException {
          DataValue value = readAttribute(AttributeId.IsAbstract);
  
          StatusCode statusCode = value.getStatusCode();
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "read IsAbstract failed");
          } else {
              Boolean isAbstract = (Boolean) value.getValue().getValue();
              setIsAbstract(isAbstract);
              return isAbstract;
          }
      }
  
      /**
       * Write a new Value attribute for this Node to the server and update the local attribute if
       * the operation succeeds.
       *
       * @param value the {@link DataValue} to write to the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public void writeValue(DataValue value) throws UaException {
          StatusCode statusCode = writeAttribute(AttributeId.Value, value);
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "write Value failed");
          } else {
              setValue(value);
          }
      }
  
      /**
       * Write a new Value attribute for this Node to the server and update the local attribute if
       * the operation succeeds.
       * <p>
       * This overload creates a {@link DataValue} with no StatusCode or timestamp values.
       *
       * @param variant the {@link Variant} to write to the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public void writeValue(Variant variant) throws UaException {
          writeValue(DataValue.valueOnly(variant));
      }
  
      /**
       * Write a new DataType attribute for this Node to the server and update the local attribute if
       * the operation succeeds.
       *
       * @param dataType the {@link NodeId} to write to the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public void writeDataType(NodeId dataType) throws UaException {
          DataValue value = DataValue.valueOnly(new Variant(dataType));
          StatusCode statusCode = writeAttribute(AttributeId.DataType, value);
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "write DataType failed");
          } else {
              setDataType(dataType);
          }
      }
  
      /**
       * Write a new ValueRank attribute for this Node to the server and update the local attribute
       * if the operation succeeds.
       *
       * @param valueRank the {@link Integer} to write to the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public void writeValueRank(Integer valueRank) throws UaException {
          DataValue value = DataValue.valueOnly(new Variant(valueRank));
          StatusCode statusCode = writeAttribute(AttributeId.ValueRank, value);
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "write ValueRank failed");
          } else {
              setValueRank(valueRank);
          }
      }
  
      /**
       * Write a new ArrayDimensions attribute for this Node to the server and update the local
       * attribute if the operation succeeds.
       *
       * @param arrayDimensions the {@link UInteger} array to write to the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public void writeArrayDimensions(UInteger[] arrayDimensions) throws UaException {
          DataValue value = DataValue.valueOnly(new Variant(arrayDimensions));
          StatusCode statusCode = writeAttribute(AttributeId.ArrayDimensions, value);
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "write ArrayDimensions failed");
          } else {
              setArrayDimensions(arrayDimensions);
          }
      }
  
      /**
       * Write a new IsAbstract attribute for this Node to the server and update the local
       * attribute if the operation succeeds.
       *
       * @param isAbstract the {@link Boolean} to write to the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public void writeIsAbstract(Boolean isAbstract) throws UaException {
          DataValue value = DataValue.valueOnly(new Variant(isAbstract));
          StatusCode statusCode = writeAttribute(AttributeId.IsAbstract, value);
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "write IsAbstract failed");
          } else {
              setIsAbstract(isAbstract);
          }
      }
  
      /**
       * Get the value of the {@link VariableTypeNodeProperties#NodeVersion} Property, if it exists.
       *
       * @return the value of the NodeVersion Property, if it exists.
       * @see VariableTypeNodeProperties
       */
      public CompletableFuture<? extends String> readNodeVersionAsync() {
          return getProperty(VariableTypeNodeProperties.NodeVersion);
      }
  
      /**
       * Set the value of the {@link VariableTypeNodeProperties#NodeVersion} Property, if it exists.
       *
       * @param nodeVersion the value to set.
       * @return a {@link CompletableFuture} that completes with the {@link StatusCode} of the write operation.
       * @see VariableTypeNodeProperties
       */
      public CompletableFuture<StatusCode> writeNodeVersionAsync(String nodeVersion) {
          return setProperty(VariableTypeNodeProperties.NodeVersion, nodeVersion);
      }
  
      @Override
      protected DataValue getAttributeValue(AttributeId attributeId) {
          switch (attributeId) {
              case Value:
                  return DataValue.valueOnly(new Variant(getValue().getValue().getValue()));
              case DataType:
                  return DataValue.valueOnly(new Variant(getDataType()));
              case ValueRank:
                  return DataValue.valueOnly(new Variant(getValueRank()));
              case ArrayDimensions:
                  return DataValue.valueOnly(new Variant(getArrayDimensions()));
              case IsAbstract:
                  return DataValue.valueOnly(new Variant(getIsAbstract()));
              default:
                  return super.getAttributeValue(attributeId);
          }
      }
  
      @Override
      protected void setAttributeValue(AttributeId attributeId, DataValue value) {
          switch (attributeId) {
              case Value: {
                  setValue(value);
                  break;
              }
              case DataType: {
                  setDataType((NodeId) value.getValue().getValue());
                  break;
              }
              case ValueRank: {
                  setValueRank((Integer) value.getValue().getValue());
                  break;
              }
              case ArrayDimensions: {
                  setArrayDimensions((UInteger[]) value.getValue().getValue());
                  break;
              }
              case IsAbstract: {
                  setIsAbstract((Boolean) value.getValue().getValue());
                  break;
              }
              default: {
                  super.setAttributeValue(attributeId, value);
              }
          }
      }
  
  }
  