private ClassDeclaration createReaderClass() {
      SimpleName parser = f.newSimpleName("parser");
      List<TypeBodyDeclaration> members = Lists.create();
      members.add(createPrivateField(CsvParser.class, parser));
      List<ExpressionStatement> constructorStatements = Lists.create();
      constructorStatements.add(mapField(parser));
      if (hasFileName()) {
        members.add(createPrivateField(StringOption.class, f.newSimpleName(FIELD_PATH_NAME)));
        constructorStatements.add(
            new ExpressionBuilder(f, f.newSimpleName(FIELD_PATH_NAME))
                .assignFrom(
                    new TypeBuilder(f, context.resolve(StringOption.class))
                        .newObject(
                            new ExpressionBuilder(f, parser).method("getPath").toExpression())
                        .toExpression())
                .toStatement());
      }
      members.add(
          f.newConstructorDeclaration(
              null,
              new AttributeBuilder(f).toAttributes(),
              f.newSimpleName(NAME_READER),
              Arrays.asList(
                  f.newFormalParameterDeclaration(context.resolve(CsvParser.class), parser)),
              constructorStatements));

      SimpleName object = f.newSimpleName("object");
      List<Statement> statements = Lists.create();
      statements.add(
          f.newIfStatement(
              new ExpressionBuilder(f, parser)
                  .method("next")
                  .apply(InfixOperator.EQUALS, Models.toLiteral(f, false))
                  .toExpression(),
              f.newBlock(
                  new ExpressionBuilder(f, Models.toLiteral(f, false)).toReturnStatement())));
      for (PropertyDeclaration property : model.getDeclaredProperties()) {
        switch (CsvFieldTrait.getKind(property, Kind.VALUE)) {
          case VALUE:
            statements.add(
                new ExpressionBuilder(f, parser)
                    .method(
                        "fill",
                        new ExpressionBuilder(f, object)
                            .method(context.getOptionGetterName(property))
                            .toExpression())
                    .toStatement());
            break;
          case FILE_NAME:
            statements.add(
                new ExpressionBuilder(f, object)
                    .method(context.getOptionSetterName(property), f.newSimpleName(FIELD_PATH_NAME))
                    .toStatement());
            break;
          case LINE_NUMBER:
            statements.add(
                new ExpressionBuilder(f, object)
                    .method(
                        context.getValueSetterName(property),
                        new ExpressionBuilder(f, parser)
                            .method("getCurrentLineNumber")
                            .toExpression())
                    .toStatement());
            break;
          case RECORD_NUMBER:
            statements.add(
                new ExpressionBuilder(f, object)
                    .method(
                        context.getValueSetterName(property),
                        new ExpressionBuilder(f, parser)
                            .method("getCurrentRecordNumber")
                            .toExpression())
                    .toStatement());
            break;
          default:
            // ignored
            break;
        }
      }
      statements.add(new ExpressionBuilder(f, parser).method("endRecord").toStatement());
      statements.add(new ExpressionBuilder(f, Models.toLiteral(f, true)).toReturnStatement());
      members.add(
          f.newMethodDeclaration(
              null,
              new AttributeBuilder(f)
                  .annotation(context.resolve(Override.class))
                  .Public()
                  .toAttributes(),
              Collections.<TypeParameterDeclaration>emptyList(),
              context.resolve(boolean.class),
              f.newSimpleName("readTo"),
              Arrays.asList(
                  f.newFormalParameterDeclaration(context.resolve(model.getSymbol()), object)),
              0,
              Arrays.asList(context.resolve(IOException.class)),
              f.newBlock(statements)));

      return f.newClassDeclaration(
          null,
          new AttributeBuilder(f).Private().Static().Final().toAttributes(),
          f.newSimpleName(NAME_READER),
          null,
          Arrays.asList(
              f.newParameterizedType(
                  context.resolve(DataModelReader.class), context.resolve(model.getSymbol()))),
          members);
    }