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.stack.core;
  
  import java.util.UUID;
  
  import com.google.common.collect.BiMap;
  import com.google.common.collect.ImmutableBiMap;
  import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
  import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
  import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
  import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject;
  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.XmlElement;
  import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UByte;
  import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
  import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.ULong;
  import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
  import org.eclipse.milo.opcua.stack.core.types.enumerated.IdType;
  import org.eclipse.milo.opcua.stack.core.util.Namespaces;
  import org.jetbrains.annotations.Nullable;
  
  public enum BuiltinDataType {
  
      Boolean(1, Boolean.class),
      SByte(2, Byte.class),
      Byte(3, UByte.class),
      Int16(4, Short.class),
      UInt16(5, UShort.class),
      Int32(6, Integer.class),
      UInt32(7, UInteger.class),
      Int64(8, Long.class),
      UInt64(9, ULong.class),
      Float(10, Float.class),
      Double(11, Double.class),
      String(12, String.class),
      DateTime(13, DateTime.class),
      Guid(14, UUID.class),
      ByteString(15, ByteString.class),
      XmlElement(16, XmlElement.class),
      NodeId(17, NodeId.class),
      ExpandedNodeId(18, org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId.class),
      StatusCode(19, StatusCode.class),
      QualifiedName(20, QualifiedName.class),
      LocalizedText(21, org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText.class),
      ExtensionObject(22, ExtensionObject.class),
      DataValue(23, org.eclipse.milo.opcua.stack.core.types.builtin.DataValue.class),
      Variant(24, Variant.class),
      DiagnosticInfo(25, org.eclipse.milo.opcua.stack.core.types.builtin.DiagnosticInfo.class);
  
      private final int typeId;
      private final Class<?> backingClass;
  
      BuiltinDataType(int typeId, Class<?> backingClass) {
          this.typeId = typeId;
          this.backingClass = backingClass;
      }
  
      public NodeId getNodeId() {
          return new NodeId(0, typeId);
      }
  
      public int getTypeId() {
          return typeId;
      }
  
      public Class<?> getBackingClass() {
          return backingClass;
      }
  
      private static final BiMap<Integer, Class<?>> BackingClassesById;
      private static final BiMap<NodeId, Class<?>> BackingClassesByNodeId;
      private static final BiMap<NodeId, BuiltinDataType> DataTypesByNodeId;
  
      static {
          ImmutableBiMap.Builder<Integer, Class<?>> builder = ImmutableBiMap.builder();
          ImmutableBiMap.Builder<NodeId, Class<?>> builder2 = ImmutableBiMap.builder();
          ImmutableBiMap.Builder<NodeId, BuiltinDataType> builder3 = ImmutableBiMap.builder();
  
          for (BuiltinDataType dataType : values()) {
              builder.put(dataType.getTypeId(), dataType.getBackingClass());
              builder2.put(dataType.getNodeId(), dataType.getBackingClass());
              builder3.put(dataType.getNodeId(), dataType);
          }
  
          BackingClassesById = builder.build();
          BackingClassesByNodeId = builder2.build();
          DataTypesByNodeId = builder3.build();
      }
  
      /**
       * @param backingClass the backing {@link Class} of the builtin type.
       * @return the id of the builtin type backed by {@code backingClass}.
       */
      public static int getBuiltinTypeId(Class<?> backingClass) {
          return BackingClassesById.inverse().get(maybeBoxPrimitive(backingClass));
      }
  
      /**
       * @param typeId the id of the builtin type.
       * @return the {@link Class} backing the builtin type.
       */
      @Nullable
      public static Class<?> getBackingClass(int typeId) {
          return BackingClassesById.get(typeId);
      }
  
      @Nullable
      public static Class<?> getBackingClass(NodeId typeId) {
          return BackingClassesByNodeId.get(typeId);
      }
  
      @Nullable
      public static Class<?> getBackingClass(ExpandedNodeId typeId) {
          if (typeId.getNamespaceIndex().intValue() == 0 && typeId.getType() == IdType.Numeric) {
              Number id = (Number) typeId.getIdentifier();
              return BackingClassesById.get(id.intValue());
          }
  
          return null;
      }
  
      @Nullable
      public static BuiltinDataType fromBackingClass(Class<?> backingClass) {
          NodeId nodeId = BackingClassesByNodeId.inverse().get(maybeBoxPrimitive(backingClass));
  
          return nodeId != null ? DataTypesByNodeId.get(nodeId) : null;
      }
  
      @Nullable
      public static BuiltinDataType fromNodeId(NodeId nodeId) {
          return DataTypesByNodeId.get(nodeId);
      }
  
      @Nullable
      public static BuiltinDataType fromNodeId(ExpandedNodeId nodeId) {
          if (nodeId.getIdentifier() instanceof UInteger &&
              (Namespaces.OPC_UA.equals(nodeId.getNamespaceUri()) || nodeId.getNamespaceIndex().intValue() == 0)) {
  
              return fromNodeId(new NodeId(0, (UInteger) nodeId.getIdentifier()));
          } else {
              return null;
          }
      }
  
      public static boolean isBuiltin(int typeId) {
          return BackingClassesById.containsKey(typeId);
      }
  
      public static boolean isBuiltin(NodeId typeId) {
          return BackingClassesByNodeId.containsKey(typeId);
      }
  
      public static boolean isBuiltin(ExpandedNodeId typeId) {
          return BuiltinDataType.fromNodeId(typeId) != null;
      }
  
      private static Class<?> maybeBoxPrimitive(Class<?> clazz) {
          if (clazz.isPrimitive()) {
              if (clazz == boolean.class) {
                  return Boolean.class;
              } else if (clazz == byte.class) {
                  return Byte.class;
              } else if (clazz == short.class) {
                  return Short.class;
              } else if (clazz == int.class) {
                  return Integer.class;
              } else if (clazz == long.class) {
                  return Long.class;
              } else if (clazz == float.class) {
                  return Float.class;
              } else if (clazz == double.class) {
                  return Double.class;
              }
          }
  
          return clazz;
      }
  
  }
  