content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  
  package org.apache.asterix.recordmanagergenerator;
  
  import java.io.IOException;
  import java.io.Reader;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.Map;
  
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.databind.node.ObjectNode;
  
  public class RecordType {
  
      enum Type {
          BYTE(1, "byte", "get", "put", "(byte)0xde", "TypeUtil.Byte.append", "TypeUtil.Byte.appendFixed"),
          SHORT(
                  2,
                  "short",
                  "getShort",
                  "putShort",
                  "(short)0xdead",
                  "TypeUtil.Short.append",
                  "TypeUtil.Short.appendFixed"),
          INT(4, "int", "getInt", "putInt", "0xdeadbeef", "TypeUtil.Int.append", "TypeUtil.Int.appendFixed"),
          GLOBAL(
                  8,
                  "long",
                  "getLong",
                  "putLong",
                  "0xdeadbeefdeadbeefl",
                  "TypeUtil.Global.append",
                  "TypeUtil.Global.appendFixed");
  
          Type(int size, String javaType, String bbGetter, String bbSetter, String deadMemInitializer, String appender,
                  String tabAppender) {
              this.size = size;
              this.javaType = javaType;
              this.bbGetter = bbGetter;
              this.bbSetter = bbSetter;
              this.deadMemInitializer = deadMemInitializer;
              this.appender = appender;
              this.tabAppender = tabAppender;
          }
  
          int size;
          String javaType;
          String bbGetter;
          String bbSetter;
          String deadMemInitializer;
          String appender;
          String tabAppender;
      }
  
      static class Field {
  
          String name;
          Type type;
          String initial;
          int offset;
          boolean accessible = true;
  
          Field(String name, Type type, String initial, int offset, boolean accessible) {
              this.name = name;
              this.type = type;
              this.initial = initial;
              this.offset = offset;
              this.accessible = accessible;
          }
  
          public static Field fromJSON(JsonNode obj) {
              String name = obj.get("name").asText();
              Type type = parseType(obj.get("type").asText());
              String initial = obj.get("initial") == null ? null : obj.get("initial").asText();
              return new Field(name, type, initial, -1, true);
          }
  
          private static Type parseType(String string) {
              string = string.toUpperCase();
              if (string.equals("GLOBAL")) {
                  return Type.GLOBAL;
              } else if (string.equals("INT")) {
                  return Type.INT;
              } else if (string.equals("SHORT")) {
                  return Type.SHORT;
              } else if (string.equals("BYTE")) {
                  return Type.BYTE;
              }
              throw new IllegalArgumentException("Unknown type \"" + string + "\"");
          }
  
          String methodName(String prefix) {
              String words[] = name.split(" ");
              assert words.length > 0;
              StringBuilder sb = new StringBuilder(prefix);
              for (int j = 0; j < words.length; ++j) {
                  String word = words[j];
                  sb.append(word.substring(0, 1).toUpperCase());
                  sb.append(word.substring(1));
              }
              return sb.toString();
          }
  
          StringBuilder appendMemoryManagerGetMethod(StringBuilder sb, String indent, int level) {
              sb = indent(sb, indent, level);
              sb.append("public ").append(type.javaType).append(' ').append(methodName("get"))
                      .append("(int slotNum) {\n");
              sb = indent(sb, indent, level + 1);
              sb.append("final Buffer buf = buffers.get(slotNum / NO_SLOTS);\n");
              sb = indent(sb, indent, level + 1);
              sb.append("buf.checkSlot(slotNum % NO_SLOTS);\n");
              sb = indent(sb, indent, level + 1);
              sb.append("final ByteBuffer b = buf.bb;\n");
              sb = indent(sb, indent, level + 1);
              sb.append("return b.").append(type.bbGetter).append("((slotNum % NO_SLOTS) * ITEM_SIZE + ")
                      .append(offsetName()).append(");\n");
              sb = indent(sb, indent, level);
              sb.append("}\n");
              return sb;
          }
  
          StringBuilder appendMemoryManagerSetMethod(StringBuilder sb, String indent, int level) {
              sb = indent(sb, indent, level);
              sb.append("public void ").append(methodName("set")).append("(int slotNum, ").append(type.javaType)
                      .append(" value) {\n");
              sb = indent(sb, indent, level + 1);
              sb.append("final ByteBuffer b = buffers.get(slotNum / NO_SLOTS).bb;\n");
              sb = indent(sb, indent, level + 1);
              sb.append("b.").append(type.bbSetter).append("((slotNum % NO_SLOTS) * ITEM_SIZE + ").append(offsetName())
                      .append(", value);\n");
              sb = indent(sb, indent, level);
              sb.append("}\n");
              return sb;
          }
  
          StringBuilder appendArenaManagerGetMethod(StringBuilder sb, String indent, int level) {
              sb = indent(sb, indent, level);
              sb.append("public ").append(type.javaType).append(' ').append(methodName("get"))
                      .append("(long slotNum) {\n");
              if (initial != null) {
                  sb = indent(sb, indent, level + 1);
                  sb.append("if (TRACK_ALLOC_ID) checkAllocId(slotNum);\n");
              }
              sb = indent(sb, indent, level + 1);
              sb.append("final int arenaId = TypeUtil.Global.arenaId(slotNum);\n");
              sb = indent(sb, indent, level + 1);
              sb.append("final int localId = TypeUtil.Global.localId(slotNum);\n");
              sb = indent(sb, indent, level + 1);
              sb.append("return get(arenaId).").append(methodName("get")).append("(localId);\n");
              sb = indent(sb, indent, level);
              sb.append("}\n");
              return sb;
          }
  
          StringBuilder appendArenaManagerSetMethod(StringBuilder sb, String indent, int level) {
              sb = indent(sb, indent, level);
              sb.append("public void ").append(methodName("set")).append("(long slotNum, ").append(type.javaType)
                      .append(" value) {\n");
              if (initial != null) {
                  sb = indent(sb, indent, level + 1);
                  sb.append("if (TRACK_ALLOC_ID) checkAllocId(slotNum);\n");
              }
              sb = indent(sb, indent, level + 1);
              sb.append("final int arenaId = TypeUtil.Global.arenaId(slotNum);\n");
              sb = indent(sb, indent, level + 1);
              sb.append("final int localId = TypeUtil.Global.localId(slotNum);\n");
              sb = indent(sb, indent, level + 1);
              sb.append("get(arenaId).").append(methodName("set")).append("(localId, value);\n");
              sb = indent(sb, indent, level);
              sb.append("}\n");
              return sb;
          }
  
          StringBuilder appendInitializers(StringBuilder sb, String indent, int level) {
              sb = indent(sb, indent, level);
              sb.append("bb.").append(type.bbSetter).append("(slotNum * ITEM_SIZE + ").append(offsetName()).append(", ");
              if (initial != null) {
                  sb.append(initial);
              } else {
                  sb.append(type.deadMemInitializer);
              }
              sb.append(");\n");
              return sb;
          }
  
          StringBuilder appendChecks(StringBuilder sb, String indent, int level) {
              if (initial == null) {
                  return sb;
              }
              sb = indent(sb, indent, level);
              sb.append("if (bb.").append(type.bbGetter).append("(itemOffset + ").append(offsetName()).append(") == ")
                      .append(type.deadMemInitializer).append(") {\n");
              sb = indent(sb, indent, level + 1);
              sb.append("String msg = \"invalid value in field ").append(offsetName())
                      .append(" of slot \" + TypeUtil.Global.toString(slotNum);\n");
              sb = indent(sb, indent, level + 1);
              sb.append("throw new IllegalStateException(msg);\n");
              sb = indent(sb, indent, level);
              sb.append("}\n");
              return sb;
          }
  
          String offsetName() {
              String words[] = name.split(" ");
              assert (words.length > 0);
              StringBuilder sb = new StringBuilder(words[0].toUpperCase());
              for (int j = 1; j < words.length; ++j) {
                  sb.append("_").append(words[j].toUpperCase());
              }
              sb.append("_OFF");
              return sb.toString();
          }
  
          int offset() {
              return offset;
          }
      }
  
      String name;
      ArrayList<Field> fields;
      int totalSize;
      boolean modifiable = true;
  
      static StringBuilder indent(StringBuilder sb, String indent, int level) {
          for (int i = 0; i < level; ++i) {
              sb.append(indent);
          }
          return sb;
      }
  
      public RecordType(String name) {
          this.name = name;
          fields = new ArrayList<Field>();
          addField("next free slot", Type.INT, "-1", false);
      }
  
      public static RecordType read(Reader reader) throws IOException {
          ObjectNode node = new ObjectMapper().readValue(reader, ObjectNode.class);
          return fromJSON(node);
      }
  
      public static RecordType fromJSON(ObjectNode obj) {
          RecordType result = new RecordType(obj.get("name").asText());
          JsonNode fields = obj.get("fields");
          for (int i = 0; i < fields.size(); i++) {
              JsonNode n = fields.get(i);
              result.fields.add(Field.fromJSON(n));
          }
          return result;
      }
  
      public void addToMap(Map<String, RecordType> map) {
          modifiable = false;
          calcOffsetsAndSize();
          map.put(name, this);
      }
  
      public void addField(String name, Type type, String initial) {
          addField(name, type, initial, true);
      }
  
      private void addField(String name, Type type, String initial, boolean accessible) {
          if (!modifiable) {
              throw new IllegalStateException("cannot modify type anmore");
          }
          fields.add(new Field(name, type, initial, -1, accessible));
      }
  
      private void calcOffsetsAndSize() {
          Collections.sort(fields, new Comparator<Field>() {
              public int compare(Field left, Field right) {
                  return right.type.size - left.type.size;
              }
          });
          // sort fields by size and align the items
          totalSize = 0;
          int alignment = 0;
          for (int i = 0; i < fields.size(); ++i) {
              final Field field = fields.get(i);
              assert field.offset == -1;
              field.offset = totalSize;
              final int size = field.type.size;
              totalSize += size;
              if (size > alignment) {
                  alignment = size;
              }
          }
          if (totalSize % alignment != 0) {
              totalSize = ((totalSize / alignment) + 1) * alignment;
          }
      }
  
      int size() {
          return fields.size();
      }
  
      static String padRight(String s, int n) {
          return String.format("%1$-" + n + "s", s);
      }
  
      static String padLeft(String s, int n) {
          return String.format("%1$" + n + "s", s);
      }
  
      StringBuilder appendConstants(StringBuilder sb, String indent, int level) {
          sb = indent(sb, indent, level);
          sb.append("public static int ITEM_SIZE = ").append(totalSize).append(";\n");
          for (int i = 0; i < fields.size(); ++i) {
              final Field field = fields.get(i);
              sb = indent(sb, indent, level);
              sb.append("public static int ").append(field.offsetName()).append(" = ").append(field.offset)
                      .append("; // size: ").append(field.type.size).append("\n");
          }
          return sb;
      }
  
      StringBuilder appendBufferPrinter(StringBuilder sb, String indent, int level) {
          int maxNameWidth = 0;
          for (int i = 0; i < fields.size(); ++i) {
              int width = fields.get(i).name.length();
              if (width > maxNameWidth) {
                  maxNameWidth = width;
              }
          }
          for (int i = 0; i < fields.size(); ++i) {
              final Field field = fields.get(i);
              sb = indent(sb, indent, level);
              sb.append("sb.append(\"").append(padRight(field.name, maxNameWidth)).append(" | \");\n");
              sb = indent(sb, indent, level);
              sb.append("for (int i = 0; i < NO_SLOTS; ++i) {\n");
              sb = indent(sb, indent, level + 1);
              sb.append(field.type.javaType).append(" value = bb.").append(field.type.bbGetter)
                      .append("(i * ITEM_SIZE + ").append(field.offsetName()).append(");\n");
              sb = indent(sb, indent, level + 1);
              sb.append("sb = ").append(field.type.tabAppender).append("(sb, value);\n");
              sb = indent(sb, indent, level + 1);
              sb.append("sb.append(\" | \");\n");
              sb = indent(sb, indent, level);
              sb.append("}\n");
              sb = indent(sb, indent, level);
              sb.append("sb.append(\"\\n\");\n");
          }
          return sb;
      }
  
      StringBuilder appendRecordPrinter(StringBuilder sb, String indent, int level) {
          sb = indent(sb, indent, level);
          sb.append("public StringBuilder appendRecord(StringBuilder sb, long slotNum) {\n");
  
          sb = indent(sb, indent, level + 1);
          sb.append("sb.append(\"{ \");\n\n");
  
          for (int i = 0; i < fields.size(); ++i) {
              Field field = fields.get(i);
              if (field.accessible) {
                  if (i > 0) {
                      sb = indent(sb, indent, level + 1);
                      sb.append("sb.append(\", \");\n\n");
                  }
                  sb = indent(sb, indent, level + 1);
                  sb.append("sb.append(\"\\\"").append(field.name).append("\\\" : \\\"\");\n");
                  sb = indent(sb, indent, level + 1);
                  sb.append("sb = ").append(field.type.appender).append("(sb, ");
                  sb.append(field.methodName("get")).append("(slotNum)");
                  sb.append(");\n");
                  sb = indent(sb, indent, level + 1);
                  sb.append("sb.append(\"\\\"\");\n\n");
              }
          }
          sb = indent(sb, indent, level + 1);
          sb.append("return sb.append(\" }\");\n");
  
          sb = indent(sb, indent, level);
          sb.append("}");
  
          return sb;
      }
  }
  