// Copyright (C) 2009 The Android Open Source Project
//
// Licensed 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 com.google.gerrit.client.data;

import java.util.ArrayList;
import java.util.List;

public class SparseFileContent {
  protected List<Range> ranges;
  protected int size;
  protected boolean missingNewlineAtEnd;

  private transient int currentRangeIdx;

  public SparseFileContent() {
    ranges = new ArrayList<Range>();
  }

  public int size() {
    return size;
  }

  public void setSize(final int s) {
    size = s;
  }

  public boolean isMissingNewlineAtEnd() {
    return missingNewlineAtEnd;
  }

  public void setMissingNewlineAtEnd(final boolean missing) {
    missingNewlineAtEnd = missing;
  }

  public String get(final int idx) {
    final String line = getLine(idx);
    if (line == null) {
      throw new ArrayIndexOutOfBoundsException(idx);
    }
    return line;
  }

  public boolean contains(final int idx) {
    return getLine(idx) != null;
  }

  private String getLine(final int idx) {
    // Most requests are sequential in nature, fetching the next
    // line from the current range, or the next range.
    //
    int high = ranges.size();
    if (currentRangeIdx < high) {
      Range cur = ranges.get(currentRangeIdx);
      if (cur.contains(idx)) {
        return cur.get(idx);
      }

      if (++currentRangeIdx < high) {
        final Range next = ranges.get(currentRangeIdx);
        if (next.contains(idx)) {
          return next.get(idx);
        }
      }
    }

    // Binary search for the range, since we know its a sorted list.
    //
    int low = 0;
    do {
      final int mid = (low + high) / 2;
      final Range cur = ranges.get(mid);
      if (cur.contains(idx)) {
        currentRangeIdx = mid;
        return cur.get(idx);
      }
      if (cur.base < idx)
        high = mid;
      else
        low = mid + 1;
    } while (low < high);
    return null;
  }

  public void addLine(final int i, final String content) {
    final Range r;
    if (!ranges.isEmpty() && i == last().end()) {
      r = last();
    } else {
      r = new Range(i);
      ranges.add(r);
    }
    r.lines.add(content);
  }

  private Range last() {
    return ranges.get(ranges.size() - 1);
  }

  static class Range {
    protected int base;
    protected List<String> lines;

    private Range(final int b) {
      base = b;
      lines = new ArrayList<String>();
    }

    protected Range() {
    }

    private String get(final int i) {
      return lines.get(i - base);
    }

    private int end() {
      return base + lines.size();
    }

    private boolean contains(final int i) {
      return base <= i && i < end();
    }
  }
}

@@ -2738,16 +2738,302 @@
 );%0A  %7D%0A%0A
+  @Override%0A  public String toString() %7B%0A    final StringBuilder b = new StringBuilder();%0A    b.append(%22SparseFileContent%5B%5Cn%22);%0A    for (Range r : ranges) %7B%0A      b.append(%22  %22);%0A      b.append(r.toString());%0A      b.append('%5Cn');%0A    %7D%0A    b.append(%22%5D%22);%0A    return b.toString();%0A  %7D%0A%0A
   static
@@ -3462,15 +3462,117 @@
 );%0A    %7D
+%0A%0A    @Override%0A    public String toString() %7B%0A      return %22Range%5B%22 + base + %22,%22 + end() + %22)%22;%0A    %7D
 %0A  %7D%0A%7D%0A

Add toString debugging aids to SparseFileContent