public static Change load(LineNumberReader reader, Pool pool) throws Exception {
    String baseColumnName = null;
    int columnInsertIndex = -1;

    List<String> columnNames = null;
    List<DBpediaType> columnTypes = null;

    List<Integer> rowIndices = null;
    List<DataExtension> dataExtensions = null;

    List<Row> oldRows = null;
    List<Row> newRows = null;

    int firstNewCellIndex = -1;

    String line;
    while ((line = reader.readLine()) != null && !"/ec/".equals(line)) {
      int equal = line.indexOf('=');
      CharSequence field = line.subSequence(0, equal);
      String value = line.substring(equal + 1);

      if ("baseColumnName".equals(field)) {
        baseColumnName = value;
      } else if ("columnInsertIndex".equals(field)) {
        columnInsertIndex = Integer.parseInt(value);
      } else if ("firstNewCellIndex".equals(field)) {
        firstNewCellIndex = Integer.parseInt(value);
      } else if ("rowIndexCount".equals(field)) {
        int count = Integer.parseInt(value);

        rowIndices = new ArrayList<Integer>(count);
        for (int i = 0; i < count; i++) {
          line = reader.readLine();
          if (line != null) {
            rowIndices.add(Integer.parseInt(line));
          }
        }
      } else if ("columnNameCount".equals(field)) {
        int count = Integer.parseInt(value);

        columnNames = new ArrayList<String>(count);
        for (int i = 0; i < count; i++) {
          line = reader.readLine();
          if (line != null) {
            columnNames.add(line);
          }
        }
      } else if ("columnTypeCount".equals(field)) {
        int count = Integer.parseInt(value);

        columnTypes = new ArrayList<DBpediaType>(count);
        for (int i = 0; i < count; i++) {
          line = reader.readLine();
          columnTypes.add(DBpediaType.load(ParsingUtilities.evaluateJsonStringToObject(line)));
        }
      } else if ("dataExtensionCount".equals(field)) {
        int count = Integer.parseInt(value);

        dataExtensions = new ArrayList<DataExtension>(count);
        for (int i = 0; i < count; i++) {
          line = reader.readLine();

          if (line == null) {
            continue;
          }

          if (line.length() == 0) {
            dataExtensions.add(null);
            continue;
          }

          int rowCount = Integer.parseInt(line);
          Object[][] data = new Object[rowCount][];

          for (int r = 0; r < rowCount; r++) {
            Object[] row = new Object[columnNames.size()];
            for (int c = 0; c < columnNames.size(); c++) {
              line = reader.readLine();

              row[c] = ReconCandidate.loadStreaming(line);
            }

            data[r] = row;
          }

          dataExtensions.add(new DataExtension(data));
        }
      } else if ("oldRowCount".equals(field)) {
        int count = Integer.parseInt(value);

        oldRows = new ArrayList<Row>(count);
        for (int i = 0; i < count; i++) {
          line = reader.readLine();
          if (line != null) {
            oldRows.add(Row.load(line, pool));
          }
        }
      } else if ("newRowCount".equals(field)) {
        int count = Integer.parseInt(value);

        newRows = new ArrayList<Row>(count);
        for (int i = 0; i < count; i++) {
          line = reader.readLine();
          if (line != null) {
            newRows.add(Row.load(line, pool));
          }
        }
      }
    }

    DBpediaDataExtensionChange change =
        new DBpediaDataExtensionChange(
            baseColumnName,
            columnInsertIndex,
            columnNames,
            columnTypes,
            rowIndices,
            dataExtensions,
            firstNewCellIndex,
            oldRows,
            newRows);

    return change;
  }
  @Override
  public void save(Writer writer, Properties options) throws IOException {
    writer.write("baseColumnName=");
    writer.write(_baseColumnName);
    writer.write('\n');
    writer.write("columnInsertIndex=");
    writer.write(Integer.toString(_columnInsertIndex));
    writer.write('\n');
    writer.write("columnNameCount=");
    writer.write(Integer.toString(_columnNames.size()));
    writer.write('\n');
    for (String name : _columnNames) {
      writer.write(name);
      writer.write('\n');
    }
    writer.write("columnTypeCount=");
    writer.write(Integer.toString(_columnTypes.size()));
    writer.write('\n');
    for (DBpediaType type : _columnTypes) {
      try {
        JSONWriter jsonWriter = new JSONWriter(writer);

        type.write(jsonWriter, options);
      } catch (JSONException e) {
        // ???
      }
      writer.write('\n');
    }
    writer.write("rowIndexCount=");
    writer.write(Integer.toString(_rowIndices.size()));
    writer.write('\n');
    for (Integer rowIndex : _rowIndices) {
      writer.write(rowIndex.toString());
      writer.write('\n');
    }
    writer.write("dataExtensionCount=");
    writer.write(Integer.toString(_dataExtensions.size()));
    writer.write('\n');
    for (DataExtension dataExtension : _dataExtensions) {
      if (dataExtension == null) {
        writer.write('\n');
        continue;
      }

      writer.write(Integer.toString(dataExtension.data.length));
      writer.write('\n');

      for (Object[] values : dataExtension.data) {
        for (Object value : values) {
          if (value == null) {
            writer.write("null");
          } else if (value instanceof ReconCandidate) {
            try {
              JSONWriter jsonWriter = new JSONWriter(writer);
              ((ReconCandidate) value).write(jsonWriter, options);
            } catch (JSONException e) {
              // ???
            }
          } else if (value instanceof String) {
            writer.write(JSONObject.quote((String) value));
          } else {
            writer.write(value.toString());
          }
          writer.write('\n');
        }
      }
    }

    writer.write("firstNewCellIndex=");
    writer.write(Integer.toString(_firstNewCellIndex));
    writer.write('\n');

    writer.write("newRowCount=");
    writer.write(Integer.toString(_newRows.size()));
    writer.write('\n');
    for (Row row : _newRows) {
      row.save(writer, options);
      writer.write('\n');
    }
    writer.write("oldRowCount=");
    writer.write(Integer.toString(_oldRows.size()));
    writer.write('\n');
    for (Row row : _oldRows) {
      row.save(writer, options);
      writer.write('\n');
    }
    writer.write("/ec/\n"); // end of change marker
  }