001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.util.struct;
006
007import java.util.ArrayList;
008import java.util.HashMap;
009import java.util.List;
010import java.util.Map;
011import java.util.Stack;
012
013/** Raw struct dynamic struct descriptor. */
014public class StructDescriptor {
015  StructDescriptor(String name) {
016    m_name = name;
017  }
018
019  /**
020   * Gets the struct name.
021   *
022   * @return name
023   */
024  public String getName() {
025    return m_name;
026  }
027
028  /**
029   * Gets the struct schema.
030   *
031   * @return schema
032   */
033  public String getSchema() {
034    return m_schema;
035  }
036
037  /**
038   * Returns whether the struct is valid (e.g. the struct is fully defined and field offsets
039   * computed).
040   *
041   * @return true if valid
042   */
043  public boolean isValid() {
044    return m_valid;
045  }
046
047  /**
048   * Returns the struct size, in bytes. Not valid unless IsValid() is true.
049   *
050   * @return size in bytes
051   * @throws IllegalStateException if descriptor is invalid
052   */
053  public int getSize() {
054    if (!m_valid) {
055      throw new IllegalStateException("descriptor is invalid");
056    }
057    return m_size;
058  }
059
060  /**
061   * Gets a field descriptor by name. Note the field cannot be accessed until the struct is valid.
062   *
063   * @param name field name
064   * @return field descriptor, or nullptr if not found
065   */
066  public StructFieldDescriptor findFieldByName(String name) {
067    return m_fieldsByName.get(name);
068  }
069
070  /**
071   * Gets all field descriptors. Note fields cannot be accessed until the struct is valid.
072   *
073   * @return field descriptors
074   */
075  public List<StructFieldDescriptor> getFields() {
076    return m_fields;
077  }
078
079  boolean checkCircular(Stack<StructDescriptor> stack) {
080    stack.push(this);
081    for (StructDescriptor ref : m_references) {
082      if (stack.contains(ref)) {
083        return false;
084      }
085      if (!ref.checkCircular(stack)) {
086        return false;
087      }
088    }
089    stack.pop();
090    return true;
091  }
092
093  void calculateOffsets(Stack<StructDescriptor> stack) {
094    int offset = 0;
095    int shift = 0;
096    int prevBitfieldSize = 0;
097    for (StructFieldDescriptor field : m_fields) {
098      if (!field.isBitField()) {
099        shift = 0; // reset shift on non-bitfield element
100        offset += prevBitfieldSize; // finish bitfield if active
101        prevBitfieldSize = 0; // previous is now not bitfield
102        field.m_offset = offset;
103        StructDescriptor struct = field.getStruct();
104        if (struct != null) {
105          if (!struct.isValid()) {
106            m_valid = false;
107            return;
108          }
109          field.m_size = struct.m_size;
110        }
111        offset += field.m_size * field.m_arraySize;
112      } else {
113        int bitWidth = field.getBitWidth();
114        if (field.getType() == StructFieldType.kBool
115            && prevBitfieldSize != 0
116            && (shift + 1) <= (prevBitfieldSize * 8)) {
117          // bool takes on size of preceding bitfield type (if it fits)
118          field.m_size = prevBitfieldSize;
119        } else if (field.m_size != prevBitfieldSize || (shift + bitWidth) > (field.m_size * 8)) {
120          shift = 0;
121          offset += prevBitfieldSize;
122        }
123        prevBitfieldSize = field.m_size;
124        field.m_offset = offset;
125        field.m_bitShift = shift;
126        shift += bitWidth;
127      }
128    }
129
130    // update struct size
131    m_size = offset + prevBitfieldSize;
132    m_valid = true;
133
134    // now that we're valid, referring types may be too
135    stack.push(this);
136    for (StructDescriptor ref : m_references) {
137      if (stack.contains(ref)) {
138        throw new IllegalStateException(
139            "internal error (inconsistent data): circular struct reference between "
140                + m_name
141                + " and "
142                + ref.m_name);
143      }
144      ref.calculateOffsets(stack);
145    }
146    stack.pop();
147  }
148
149  private final String m_name;
150  String m_schema;
151  final List<StructDescriptor> m_references = new ArrayList<>();
152  final List<StructFieldDescriptor> m_fields = new ArrayList<>();
153  final Map<String, StructFieldDescriptor> m_fieldsByName = new HashMap<>();
154  int m_size;
155  boolean m_valid;
156}