/**
   * Reads line by line and creates new entities
   *
   * @throws IOException
   * @throws IfcNotFoundException
   * @throws IfcParserException
   */
  public List<IfcEntity> parseEntities(
      IfcLineReader reader, IfcSchema schema, boolean isHeaderSection, boolean ignoreUnknownTypes)
      throws IOException, IfcNotFoundException, IfcParserException {

    this.schema = schema;
    this.reader = reader;

    List<IfcEntity> entities = new ArrayList<>();

    String statement;
    String[] tokens;
    //
    // getting entity headers
    //
    while ((statement = reader.getNextStatement()) != null) {

      IfcEntity entity;
      String entityAttributesString;

      if (!isHeaderSection) {
        tokens = RegexUtils.split2(statement, IfcVocabulary.StepFormat.LINE_NUMBER);
        if (tokens.length != 2) {
          continue;
        }

        tokens = RegexUtils.split2(tokens[1], IfcVocabulary.StepFormat.EQUAL);

        //
        // create entity
        //
        long lineNumber = Long.parseLong(tokens[0].trim());
        entity = getEntity(lineNumber);
        entityAttributesString = tokens[1].trim();

      } else {
        entity = new IfcEntity(0);
        entityAttributesString = statement;
      }

      //
      // set entity type
      //
      int indexOfOpeningBracket = entityAttributesString.indexOf(StringUtils.OPENING_ROUND_BRACKET);
      String entityTypeInfoName = entityAttributesString.substring(0, indexOfOpeningBracket).trim();

      IfcEntityTypeInfo entityTypeInfo;

      try {
        entityTypeInfo = schema.getEntityTypeInfo(entityTypeInfoName);
      } catch (IfcNotFoundException e) {
        if (ignoreUnknownTypes) {
          continue;
        } else {
          throw e;
        }
      }
      entity.setTypeInfo(entityTypeInfo);

      entityAttributesString =
          entityAttributesString.substring(
              indexOfOpeningBracket + 1, entityAttributesString.length() - 1);

      List<IfcAttributeInfo> attributeInfos = entityTypeInfo.getInheritedAttributeInfos();

      //
      // parse attribute string to get attribute values
      //
      List<IfcValue> attributeValues =
          parseAttributeValues(
              new StrBuilderWrapper(entityAttributesString), entity, attributeInfos, null, null);

      setEntityAttributeValues(entity, attributeInfos, attributeValues);

      //
      // add entity to the model
      //
      entities.add(entity);
    }

    if (!isHeaderSection) {
      for (IfcEntity entity : entities) {
        entity.bindInverseLinks();
      }
    }

    return entities;
  }
  /**
   * Parses an entity's attribute string to get attribute values
   *
   * @param attributeStringBuilder
   * @param attributeValueType
   * @return a single attribute value or list of attribute values
   * @throws IfcFormatException
   * @throws IfcNotFoundException
   * @throws IfcValueTypeConflictException
   */
  private List<IfcValue> parseAttributeValues(
      StrBuilderWrapper attributeStrBuilderWrapper,
      IfcEntity entity,
      List<IfcAttributeInfo> entityAttributeInfos,
      IfcTypeInfo commonAttributeTypeInfo,
      EnumSet<IfcTypeEnum> commonValueTypes)
      throws IfcFormatException, IfcNotFoundException {

    logger.debug(String.format("Parsing entity '%s'", entity));

    List<IfcValue> attributeValues = new ArrayList<>();

    for (int attributeIndex = 0;
        !attributeStrBuilderWrapper.trimLeft().isEmpty();
        ++attributeIndex) {

      EnumSet<IfcTypeEnum> attributeValueTypes;
      IfcAttributeInfo attributeInfo;
      IfcTypeInfo attributeTypeInfo;
      if (commonValueTypes == null) {
        assert (attributeIndex < entityAttributeInfos.size())
            : String.format(
                "attributeIndex=%d, entityAttributeInfos.size=%s, attributeStrBuilderWrapper='%s'",
                attributeIndex, entityAttributeInfos, attributeStrBuilderWrapper);
        attributeInfo = entityAttributeInfos.get(attributeIndex);
        attributeTypeInfo = attributeInfo.getAttributeTypeInfo();
        attributeValueTypes = attributeTypeInfo.getValueTypes();
      } else {
        assert (commonAttributeTypeInfo != null);
        attributeInfo = entityAttributeInfos.get(0);
        attributeTypeInfo = commonAttributeTypeInfo;
        attributeValueTypes = commonValueTypes;
      }

      if (attributeTypeInfo instanceof IfcCollectionTypeInfo) {
        attributeTypeInfo = ((IfcCollectionTypeInfo) attributeTypeInfo).getItemTypeInfo();
      }

      switch (attributeStrBuilderWrapper.charAt(0)) {
        case IfcVocabulary.StepFormat.LINE_NUMBER_SYMBOL: // Entity
          attributeStrBuilderWrapper.skip(1);
          Long remoteLineNumber = attributeStrBuilderWrapper.getLong();
          IfcEntity remoteEntity = getEntity(remoteLineNumber);
          if (remoteEntity == null) {
            throw new IfcNotFoundException("Entity not found: #" + remoteLineNumber);
          }
          attributeValues.add(remoteEntity);
          break;

        case IfcVocabulary.StepFormat.STRING_VALUE_SYMBOL:
          String s = attributeStrBuilderWrapper.getStringBetweenSingleQuotes();
          assert attributeValueTypes.size() == 1 : "Expect attributeValueTypes.size() == 1";
          //				if (!attributeValueTypes.contains(IfcTypeEnum.GUID)) {
          attributeValues.add(new IfcLiteralValue(s, attributeTypeInfo, IfcTypeEnum.STRING));
          //					break;
          //				} else {
          //					attributeValues.add(new IfcGuidValue(s));
          //					break;
          //				}
          break;

        case IfcVocabulary.StepFormat.ENUMERATION_VALUE_SYMBOL:
          s =
              attributeStrBuilderWrapper.getStringBetweenSimilarCharacters(
                  IfcVocabulary.StepFormat.ENUMERATION_VALUE_SYMBOL);

          assert attributeValueTypes.size() == 1 : "Expect attributeValueTypes.size() == 1";
          if (!attributeValueTypes.contains(IfcTypeEnum.LOGICAL)) {
            attributeValues.add(new IfcLiteralValue(s, attributeTypeInfo, IfcTypeEnum.ENUM));
          } else {
            switch (s) {
              case "T":
              case "TRUE":
                attributeValues.add(
                    new IfcLiteralValue(
                        LogicalEnum.TRUE.toString(), attributeTypeInfo, IfcTypeEnum.LOGICAL));
                break;
              case "F":
              case "FALSE":
                attributeValues.add(
                    new IfcLiteralValue(
                        LogicalEnum.FALSE.toString(), attributeTypeInfo, IfcTypeEnum.LOGICAL));
                break;
              default:
                attributeValues.add(
                    new IfcLiteralValue(
                        LogicalEnum.UNKNOWN.toString(), attributeTypeInfo, IfcTypeEnum.LOGICAL));
                break;
            }
          }
          break;

        case IfcVocabulary.StepFormat.NULL_SYMBOL: // $
          attributeValues.add(IfcValue.NULL);
          attributeStrBuilderWrapper.skip(1);
          break;

        case IfcVocabulary.StepFormat.ANY_SYMBOL: // *
          attributeValues.add(IfcValue.ANY);
          attributeStrBuilderWrapper.skip(1);
          break;

        case StringUtils.OPENING_ROUND_BRACKET_CHAR: // List or Set
          String stringBetweenBrackets = attributeStrBuilderWrapper.getStringBetweenRoundBrackets();

          StrBuilderWrapper sbWrapper = new StrBuilderWrapper(stringBetweenBrackets);

          List<IfcAttributeInfo> attributeInfos = new ArrayList<>(1);
          attributeInfos.add(attributeInfo);

          List<IfcValue> values =
              parseAttributeValues(
                  sbWrapper, null, attributeInfos, attributeTypeInfo, attributeValueTypes);
          attributeValues.add(new IfcTemporaryCollectionValueWrapper(values));
          break;

        default:
          if (Character.isAlphabetic(attributeStrBuilderWrapper.charAt(0))) {

            //
            // parsing sub entity
            //
            String subEntityTypeInfoName = attributeStrBuilderWrapper.getIdentifierName();
            IfcNonEntityTypeInfo subNonEntityTypeInfo =
                schema.getNonEntityTypeInfo(subEntityTypeInfoName);
            attributeValueTypes = subNonEntityTypeInfo.getValueTypes();
            s = attributeStrBuilderWrapper.getStringBetweenRoundBrackets();

            assert (s != null);

            attributeInfos = new ArrayList<>(1);
            attributeInfos.add(attributeInfo);

            values =
                parseAttributeValues(
                    new StrBuilderWrapper(s),
                    null,
                    attributeInfos,
                    subNonEntityTypeInfo,
                    attributeValueTypes);
            assert values.size() == 1
                : "Expect only 1 argument: " + entity + ":" + values.toString();
            // attributeValues.add(new IfcShortEntity(subNonEntityTypeInfo,
            // (IfcLiteralValue)values.get(0)));
            attributeValues.add((IfcLiteralValue) values.get(0));
          } else {

            //
            // parsing number or datetime
            //
            assert attributeValueTypes.size() == 1 : "Expect attributeValueTypes.size() == 1";
            IfcTypeEnum attributeValueType = (IfcTypeEnum) attributeValueTypes.toArray()[0];
            Object value;
            if (attributeValueType == IfcTypeEnum.INTEGER) {
              value = attributeStrBuilderWrapper.getLong();
            } else if (attributeValueType == IfcTypeEnum.REAL) {
              value = attributeStrBuilderWrapper.getDouble();
            } else {
              assert attributeValueType == IfcTypeEnum.DATETIME;
              long timeStamp = attributeStrBuilderWrapper.getLong();
              value = Calendar.getInstance();
              ((Calendar) value).setTimeInMillis(timeStamp * 1000);
            }

            attributeValues.add(
                new IfcLiteralValue(
                    value, (IfcNonEntityTypeInfo) attributeTypeInfo, attributeValueType));
          }

          break;
      }

      attributeStrBuilderWrapper.trimLeft();
      attributeStrBuilderWrapper.getFirstMatch(StrMatcher.commaMatcher());
    }

    return attributeValues;
  }