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.MethodNode;
  import org.eclipse.milo.opcua.sdk.core.nodes.MethodNodeProperties;
  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.eclipse.milo.opcua.stack.core.types.structured.Argument;
  
  public class UaMethodNode extends UaNode implements MethodNode {
  
      private Boolean executable;
      private Boolean userExecutable;
  
      public UaMethodNode(
          OpcUaClient client,
          NodeId nodeId,
          NodeClass nodeClass,
          QualifiedName browseName,
          LocalizedText displayName,
          LocalizedText description,
          UInteger writeMask,
          UInteger userWriteMask,
          Boolean executable,
          Boolean userExecutable
      ) {
  
          super(client, nodeId, nodeClass, browseName, displayName, description, writeMask, userWriteMask);
  
          this.executable = executable;
          this.userExecutable = userExecutable;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The returned attribute is the most recently seen value; it is not read live from the server.
       *
       * @see #readExecutable()
       */
      @Override
      public synchronized Boolean isExecutable() {
          return executable;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The returned attribute is the most recently seen value; it is not read live from the server.
       *
       * @see #readUserExecutable()
       */
      @Override
      public synchronized Boolean isUserExecutable() {
          return userExecutable;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The attribute is only updated locally; it is not written to the server.
       *
       * @see #writeExecutable(Boolean)
       */
      @Override
      public synchronized void setExecutable(Boolean executable) {
          this.executable = executable;
      }
  
      /**
       * {@inheritDoc}
       * <p>
       * The attribute is only updated locally; it is not written to the server.
       *
       * @see #writeUserExecutable(Boolean)
       */
      @Override
      public synchronized void setUserExecutable(Boolean userExecutable) {
          this.userExecutable = userExecutable;
      }
  
      /**
       * Read the Executable 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.s
       */
      public Boolean readExecutable() throws UaException {
          DataValue value = readAttribute(AttributeId.Executable);
  
          StatusCode statusCode = value.getStatusCode();
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "read Executable failed");
          } else {
              Boolean executable = (Boolean) value.getValue().getValue();
              setExecutable(executable);
              return executable;
          }
      }
  
      /**
       * Read the UserExecutable 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.s
       */
      public Boolean readUserExecutable() throws UaException {
          DataValue value = readAttribute(AttributeId.UserExecutable);
  
          StatusCode statusCode = value.getStatusCode();
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "read UserExecutable failed");
          } else {
              Boolean userExecutable = (Boolean) value.getValue().getValue();
              setUserExecutable(userExecutable);
              return userExecutable;
          }
      }
  
      /**
       * Write a new Executable attribute for this Node to the server and update the local attribute
       * if the operation succeeds.
       *
       * @param executable the {@link Boolean} to write to the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public void writeExecutable(Boolean executable) throws UaException {
          DataValue value = DataValue.valueOnly(new Variant(executable));
          StatusCode statusCode = writeAttribute(AttributeId.Executable, value);
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "write Executable failed");
          } else {
              setExecutable(executable);
          }
      }
  
      /**
       * Write a new UserExecutable attribute for this Node to the server and update the local
       * attribute if the operation succeeds.
       *
       * @param userExecutable the {@link Boolean} to write to the server.
       * @throws UaException if a service- or operation-level error occurs.
       */
      public void writeUserExecutable(Boolean userExecutable) throws UaException {
          DataValue value = DataValue.valueOnly(new Variant(userExecutable));
          StatusCode statusCode = writeAttribute(AttributeId.UserExecutable, value);
  
          if (statusCode != null && statusCode.isBad()) {
              throw new UaException(statusCode, "write UserExecutable failed");
          } else {
              setUserExecutable(userExecutable);
          }
      }
  
      /**
       * Get the value of the {@link MethodNodeProperties#NodeVersion} Property, if it exists.
       * <p>
       * The NodeVersion Property is used to indicate the version of a Node.
       * <p>
       * The NodeVersion Property is updated each time a Reference is added or deleted to the Node the Property belongs
       * to. Attribute value changes do not cause the NodeVersion to change. Clients may read the NodeVersion Property or
       * subscribe to it to determine when the structure of a Node has changed.
       * <p>
       * This Property is optional. If not present, the future will be completed exceptionally with a {@link UaException}
       * indicating {@link StatusCodes#Bad_NotFound}.
       *
       * @return the value of the NodeVersion Property, if it exists.
       * @see MethodNodeProperties
       */
      public CompletableFuture<? extends String> readNodeVersionAsync() {
          return getProperty(MethodNodeProperties.NodeVersion);
      }
  
      /**
       * Get the value of the {@link MethodNodeProperties#InputArguments} Property, if it exists.
       * <p>
       * The InputArguments Property is used to specify the arguments that shall be used by a client when calling the
       * Method.
       * <p>
       * This Property is optional. If not present, the future will be completed exceptionally with a {@link UaException}
       * indicating {@link StatusCodes#Bad_NotFound}.
       *
       * @return the value of the InputArguments Property, if it exists.
       * @see MethodNodeProperties
       */
      public CompletableFuture<Argument[]> readInputArgumentsAsync() {
          return getProperty(MethodNodeProperties.InputArguments);
      }
  
      /**
       * Get the value of the {@link MethodNodeProperties#OutputArguments} Property, if it exists.
       * <p>
       * The OutputArguments Property specifies the result returned from the Method call.
       * <p>
       * This Property is optional. If not present, the future will be completed exceptionally with a {@link UaException}
       * indicating {@link StatusCodes#Bad_NotFound}.
       *
       * @return the value of the OutputArguments Property, if it exists.
       * @see MethodNodeProperties
       */
      public CompletableFuture<Argument[]> readOutputArgumentsAsync() {
          return getProperty(MethodNodeProperties.OutputArguments);
      }
  
      /**
       * Set the value of the {@link MethodNodeProperties#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 MethodNodeProperties
       */
      public CompletableFuture<StatusCode> writeNodeVersionAsync(String nodeVersion) {
          return setProperty(MethodNodeProperties.NodeVersion, nodeVersion);
      }
  
      /**
       * Set the value of the {@link MethodNodeProperties#InputArguments} Property, if it exists.
       *
       * @param inputArguments the value to set.
       * @return a {@link CompletableFuture} that completes with the {@link StatusCode} of the write operation.
       * @see MethodNodeProperties
       */
      public CompletableFuture<StatusCode> writeInputArgumentsAsync(Argument[] inputArguments) {
          return setProperty(MethodNodeProperties.InputArguments, inputArguments);
      }
  
      /**
       * Set the value of the {@link MethodNodeProperties#OutputArguments} Property, if it exists.
       *
       * @param outputArguments the value to set.
       * @return a {@link CompletableFuture} that completes with the {@link StatusCode} of the write operation.
       * @see MethodNodeProperties
       */
      public CompletableFuture<StatusCode> writeOutputArgumentsAsync(Argument[] outputArguments) {
          return setProperty(MethodNodeProperties.OutputArguments, outputArguments);
      }
  
      @Override
      protected DataValue getAttributeValue(AttributeId attributeId) {
          switch (attributeId) {
              case Executable:
                  return DataValue.valueOnly(new Variant(isExecutable()));
              case UserExecutable:
                  return DataValue.valueOnly(new Variant(isUserExecutable()));
              default:
                  return super.getAttributeValue(attributeId);
          }
      }
  
      @Override
      protected void setAttributeValue(AttributeId attributeId, DataValue value) {
          switch (attributeId) {
              case Executable: {
                  setExecutable((Boolean) value.getValue().getValue());
                  break;
              }
              case UserExecutable: {
                  setUserExecutable((Boolean) value.getValue().getValue());
                  break;
              }
              default: {
                  super.setAttributeValue(attributeId, value);
              }
          }
      }
  
  }
  