code: /*
   * Copyright (c) 2019 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.core;
  
  import java.lang.reflect.Array;
  import java.util.Arrays;
  
  import com.google.common.base.MoreObjects;
  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.ByteString;
  import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
  import org.eclipse.milo.opcua.stack.core.util.ArrayUtil;
  
  public final class NumericRange {
  
      private final String range;
      private final Bounds[] bounds;
  
      public NumericRange(String range, Bounds[] bounds) {
          this.range = range;
          this.bounds = bounds;
      }
  
      public String getRange() {
          return range;
      }
  
      public Bounds[] getBounds() {
          return bounds;
      }
  
      @Override
      public String toString() {
          return MoreObjects.toStringHelper(this)
              .add("range", range)
              .toString();
      }
  
      private int getDimensionCount() {
          return bounds.length;
      }
  
      private Bounds getDimensionBounds(int dimension) {
          return bounds[dimension - 1];
      }
  
      public static final class Bounds {
          private final int low;
          private final int high;
  
          private Bounds(int low, int high) throws UaException {
              if (low < 0 || high < 0 || low > high) throw new UaException(StatusCodes.Bad_IndexRangeInvalid);
  
              this.low = low;
              this.high = high;
          }
  
          public int getLow() {
              return low;
          }
  
          public int getHigh() {
              return high;
          }
  
          @Override
          public String toString() {
              return MoreObjects.toStringHelper(this)
                  .add("low", low)
                  .add("high", high)
                  .toString();
          }
      }
  
      public static NumericRange parse(String range) throws UaException {
          try {
              String[] ss = range.split(",");
              Bounds[] bounds = new Bounds[ss.length];
  
              for (int i = 0; i < ss.length; i++) {
                  String s = ss[i];
                  String[] bs = s.split(":");
  
                  if (bs.length == 1) {
                      int index = Integer.parseInt(bs[0]);
                      bounds[i] = new Bounds(index, index);
                  } else if (bs.length == 2) {
                      int low = Integer.parseInt(bs[0]);
                      int high = Integer.parseInt(bs[1]);
  
                      if (low == high) throw new UaException(StatusCodes.Bad_IndexRangeInvalid);
  
                      bounds[i] = new Bounds(low, high);
                  } else {
                      throw new UaException(StatusCodes.Bad_IndexRangeInvalid);
                  }
              }
  
              return new NumericRange(range, bounds);
          } catch (Throwable ex) {
              throw new UaException(StatusCodes.Bad_IndexRangeInvalid, ex);
          }
      }
  
      public static Object readFromValueAtRange(Variant value, NumericRange range) throws UaException {
          Object array = value.getValue();
  
          if (array == null) {
              throw new UaException(StatusCodes.Bad_IndexRangeNoData);
          }
  
          if (!array.getClass().isArray()) {
              if (!(array instanceof String) && !(array instanceof ByteString)) {
                  throw new UaException(StatusCodes.Bad_IndexRangeNoData);
              }
          }
  
          try {
              return readFromValueAtRange(array, range, 1);
          } catch (Throwable ex) {
              throw new UaException(StatusCodes.Bad_IndexRangeNoData, ex);
          }
      }
  
      private static Object readFromValueAtRange(Object array, NumericRange range, int dimension) throws UaException {
          int dimensionCount = range.getDimensionCount();
          Bounds bounds = range.getDimensionBounds(dimension);
          int low = bounds.getLow();
          int high = bounds.getHigh();
  
          if (dimension == dimensionCount) {
              if (array.getClass().isArray()) {
                  int len = Math.min(high + 1, Array.getLength(array)) - low;
                  Class<?> type = array.getClass().getComponentType();
                  Object a = Array.newInstance(type, len);
  
                  for (int i = 0; i < len; i++) {
                      Object element = Array.get(array, low + i);
                      Array.set(a, i, element);
                  }
  
                  return a;
              } else if (array instanceof String) {
                  String s = (String) array;
                  int to = Math.min(high + 1, s.length());
                  return s.substring(low, to);
              } else if (array instanceof ByteString) {
                  ByteString bs = (ByteString) array;
                  int to = Math.min(high + 1, bs.length());
                  byte[] copy = Arrays.copyOfRange(bs.bytesOrEmpty(), low, to);
                  return new ByteString(copy);
              } else {
                  throw new UaException(StatusCodes.Bad_IndexRangeNoData);
              }
          } else {
              int len = Math.min(high + 1, Array.getLength(array)) - low;
              Class<?> type = array.getClass().getComponentType();
              Object a = Array.newInstance(type, len);
  
              for (int i = 0; i < len; i++) {
                  Object na = Array.get(array, low + i);
                  Object element = readFromValueAtRange(na, range, dimension + 1);
                  Array.set(a, i, element);
              }
  
              return a;
          }
      }
  
      public static Object writeToValueAtRange(Variant currentVariant,
                                               Variant updateVariant,
                                               NumericRange range) throws UaException {
  
          Object current = currentVariant.getValue();
          Object update = updateVariant.getValue();
  
          if (current == null || update == null) {
              throw new UaException(StatusCodes.Bad_IndexRangeNoData);
          }
  
          try {
              return writeToValueAtRange(current, update, range, 1);
          } catch (Throwable ex) {
              throw new UaException(StatusCodes.Bad_IndexRangeNoData, ex);
          }
      }
  
      private static Object writeToValueAtRange(Object current,
                                                Object update,
                                                NumericRange range,
                                                int dimension) throws UaException {
  
          int dimensionCount = range.getDimensionCount();
          Bounds bounds = range.getDimensionBounds(dimension);
          int low = bounds.getLow();
          int high = bounds.getHigh();
  
          if (dimension == dimensionCount) {
              if (current.getClass().isArray()) {
                  Class<?> currentType = ArrayUtil.getType(current);
                  Class<?> updateType = ArrayUtil.getType(update);
  
                  if (currentType != updateType) {
                      throw new UaException(
                          StatusCodes.Bad_TypeMismatch,
                          String.format("currentType=%s, updateType=%s", current, update));
                  }
  
                  int length = Array.getLength(current);
                  Object copy = Array.newInstance(currentType, length);
  
                  if (low >= length || high >= length) {
                      throw new UaException(StatusCodes.Bad_IndexRangeNoData);
                  }
  
                  for (int i = 0; i < length; i++) {
                      if (i < low || i > high) {
                          Object element = Array.get(current, i);
                          Array.set(copy, i, element);
                      } else {
                          Object element = Array.get(update, i - low);
                          Array.set(copy, i, element);
                      }
                  }
  
                  return copy;
              } else if (current instanceof String) {
                  String cs = (String) current;
                  String us = (String) update;
                  int length = cs.length();
                  StringBuilder copy = new StringBuilder();
  
                  if (low >= length || high >= length) {
                      throw new UaException(StatusCodes.Bad_IndexRangeNoData);
                  }
  
                  for (int i = 0; i < length; i++) {
                      if (i < low || i > high) {
                          copy.append(cs.charAt(i));
                      } else {
                          copy.append(us.charAt(i - low));
                      }
                  }
  
                  return copy.toString();
              } else if (current instanceof ByteString) {
                  ByteString bs = (ByteString) current;
                  ByteString us = (ByteString) update;
                  int length = bs.length();
                  byte[] copy = new byte[length];
  
                  if (low >= length || high >= length) {
                      throw new UaException(StatusCodes.Bad_IndexRangeNoData);
                  }
  
                  for (int i = 0; i < length; i++) {
                      if (i < low || i > high) {
                          copy[i] = bs.byteAt(i);
                      } else {
                          copy[i] = us.byteAt(i - low);
                      }
                  }
  
                  return new ByteString(copy);
              } else {
                  throw new UaException(StatusCodes.Bad_IndexRangeNoData);
              }
          } else {
              Class<?> type = current.getClass().getComponentType();
              int length = Array.getLength(current);
              Object copy = Array.newInstance(type, length);
  
              if (low >= length || high >= length) {
                  throw new UaException(StatusCodes.Bad_IndexRangeNoData);
              }
  
              for (int i = 0; i < length; i++) {
                  if (i < low || i > high) {
                      Object element = Array.get(current, i);
                      Array.set(copy, i, element);
                  } else {
                      Object c = Array.get(current, i);
                      Object u = Array.get(update, i - low);
                      Object element = writeToValueAtRange(c, u, range, dimension + 1);
                      Array.set(copy, i, element);
                  }
              }
  
              return copy;
          }
      }
  
  
  }
  