3

I'm trying to implement a custom FilterReader class. The class is supposed to transform what it reads (never mind how). The problem I'm having is that the string after transformation is longer than the original one read into the char buffer, so I get an ArrayIndexOutOfBoundsException when I try to cram the new string into the buffer.

Here's the read() method of my custom FilterReader class:

@Override
public int read(char[] cbuf, int off, int len) throws IOException {
    int result = in.read(cbuf, off, len);

    if( result != -1 ){
        String str = new String(cbuf, off, len);
        str = someStringTranformationMethod(str);

        //cbuf = new char[str.length()];

        str.getChars(0, str.length(), cbuf, 0);

        result = str.length();
    }

    return result;
}

I thought I could solve it by reallocating a new char buffer for cbuf, which is what I tried to at the commented line. However, this doesn't seem to work at all, because then my output from the reader is the original (untransformed) string.

I have a feeling I'm going about things completely backwards here, but it's not easy to find any good examples of this stuff online. All the custom FilterReaders I have found have just done some basic upper/lowercasing of the characters, where the length of the new string is the same as the original one.

So how would I implement this with a string transformation function that results in a longer string than the original one?

Magnus
  • 17,157
  • 19
  • 104
  • 189
  • If there's an upper bound for the quotient of str.lengths after and before transformation, the read can reduce the length accordingly, so the transformated chars will still fit in the buffer. – laune Mar 19 '16 at 21:00
  • As a (more complex) alternative, someStringTranformationMethod would have to know about its maximum output length and cache the excess characters, to be forwarded with the next call. In this case, the transformer should provide a method to inform the caller about the length of pending data, so that a call without an additional read is indicated. (This to avoid unbounded accumulation of cached data.) – laune Mar 19 '16 at 21:04
  • Based on the value of `len`, can you know the length of the resulting string? If so, can't you calculate the number of characters to read from the source stream that will give you `len` character output? – WeaponsGrade Mar 19 '16 at 21:16
  • @WeaponsGrade even if that information would be available, if wouldn't help for the case where one character is requested but it is transformed to two characters. – Stefan Haustein Mar 19 '16 at 21:25
  • @StefanHaustein in your case, you return the single character, and yes, you'd have to buffer the extra. It's a stream after all, so it's implied that more characters are available until -1 is returned. – WeaponsGrade Mar 19 '16 at 21:27
  • @WeaponsGrade what would be the benefit if you need to buffer anyway? – Stefan Haustein Mar 19 '16 at 21:29
  • @BadCash note that `result` may be < `len`, so where you create the new string, the third parameter should be `result`. – Stefan Haustein Mar 19 '16 at 21:37
  • @StefanHaustein I'm just trying to get more information about _the problem_, not offer a solution. You are supposing that's the case, and I'd rather know if it's deterministic. If so, a small calculation makes it easy to answer. If not, that's a useful clarification to the question. – WeaponsGrade Mar 19 '16 at 21:52

1 Answers1

2

You'll need to make your implementation stateful and keep track of the "leftover" characters:

private String str = "";
private int pos = 0;

public int read(char[] cbuf, int off, int len) throws IOException {
  if (pos == str.length()) {
    // No leftovers from a previous call available, need to actully read more
    int result = in.read(cbuf, off, len);
    if( result <= 0 ){
      return -1;
    }
    str = new String(cbuf, off, result);
    str = someStringTranformationMethod(str);
    pos = 0;
  }

  // Return as much as we have available, but not more than len
  int available = Math.min(str.length() - pos, len);     
  str.getChars(pos, pos + available, cbuf, off);
  pos += available;
  return available;
}

Note that read() only needs to read only (at least) one character if the end of the stream has not been reached. This implementation takes advantage of this.

Stefan Haustein
  • 18,427
  • 3
  • 36
  • 51