Правильный способ форматирования входного потока - PullRequest
2 голосов
/ 03 января 2011

У меня следующая проблема: моей программе передан InputStream, содержимое которого я не могу контролировать. Я отменяю поток ввода, используя библиотеку javax, которая по праву выдает исключения, если InputStream включает символ &, за которым не следует «amp;»

Обходной путь, который я нашел, заключался в создании следующего класса:

import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.InputStream;

/**
 * Provide an input stream where all & characters are properly encoded as &
 */
public class FormattedStream extends FilterInputStream {
  public FormattedStream(InputStream src) {
    super(new ByteArrayInputStream(StringUtil.toString(src)
      .replace("&", "&").replace("amp;amp;", "amp;").getBytes()));
  }
}

Примечание: StringUtil - это простая утилита, мне нужно превратить входной поток в строку.

С этим классом я теперь вызываю демаршаллер JAXB с:

unmarshal(new FormattedStream(inputStream));

вместо

unmarshal(inputStream);

Этот подход работает, но кажется странным по нескольким причинам:

1 - Из-за ограничения, что super должен быть первым элементом в конструкторе (ограничение, которое я не могу понять, несмотря на то, что я о нем читал), я вынужден выполнять всю свою обработку в одной строке, делая код далеко из читабельного.

2 - Преобразование всего потока в строку и обратно в поток кажется излишним

3 - приведенный выше код немного некорректен в том, что поток содержит amp; amp; будет изменен на содержащий ампер

Я мог бы обратиться к 1, предоставив класс FormatInputStream одним методом:

InputStream preProcess(InputStream inputStream)

, где я выполнял бы те же операции, которые я сейчас выполняю в конструкторе моего класса FormattedStream, но кажется странным, что приходится выбирать другой интерфейс из-за ограничения кодирования.

Я мог бы обратиться к 2, сохранив простой конструктор FormattedStream:

super(src)

и переопределение трех методов чтения, но это потребовало бы гораздо больше кодирования: переопределение трех методов чтения путем замены & на лету не тривиально по сравнению с одной строкой кода, который у меня есть на данный момент, где я могу использовать replaceAll String способ.

Что касается 3, то, похоже, достаточно углового футляра, чтобы я не волновался об этом, но, возможно, мне следует ...

Любые предложения о том, как решить мою проблему более элегантным способом?

Ответы [ 2 ]

3 голосов
/ 03 января 2011

Я согласен с ответом МакДауэлла о том, что самое главное - это исправить неверный источник данных .

В любом случае, здесь InputStream, который ищет одинокого & символов и женится на них с дополнительными amp; на случай, если их не хватает.Опять же, исправление поврежденных данных таким способом не окупается большую часть времени.

Это решение исправляет три недостатка, упомянутых в OP, и показывает только один способ реализовать преобразование InputStreams.

  • Внутри конструктора сохраняется только ссылка на исходный InputStream. В конструкторе не выполняется обработка , пока поток действительно не запросит данные (посредством вызовов read ()).
  • Содержимое не преобразовано в одну большую строку для трансформации.Вместо этого поток работает как поток и выполняет только минимальное опережающее чтение (например, четыре байта, необходимые для определения, следует ли &, затем amp; или нет.
  • Поток заменяет только одинокий & и не пытается очистить amp;amp; в любом случае, потому что этого не происходит с этим решением.

.

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.Deque;

public class ReplacerInputStream extends InputStream {

private static final byte[] REPLACEMENT = "amp;".getBytes();
    private final byte[] readBuf = new byte[REPLACEMENT.length];
    private final Deque<Byte> backBuf = new ArrayDeque<Byte>();
    private final InputStream in;

    public ReplacerInputStream(InputStream in) {
        this.in = in;
    }

    @Override
    public int read() throws IOException {
        if (!backBuf.isEmpty()) {
            return backBuf.pop();
        }
        int first = in.read();
        if (first == '&') {
            peekAndReplace();
        }
        return first;
    }

    private void peekAndReplace() throws IOException {
        int read = super.read(readBuf, 0, REPLACEMENT.length);
        for (int i1 = read - 1; i1 >= 0; i1--) {
            backBuf.push(readBuf[i1]);
        }
        for (int i = 0; i < REPLACEMENT.length; i++) {
            if (read != REPLACEMENT.length || readBuf[i] != REPLACEMENT[i]) {
                for (int j = REPLACEMENT.length - 1; j >= 0; j--) {
                    // In reverse order
                    backBuf.push(REPLACEMENT[j]);
                }
                return;
            }
        }
    }

}

Код был протестирован со следующими входными данными (первый параметр - ожидаемый вывод, второй параметр - необработанный ввод):

    test("Foo &amp; Bar", "Foo & Bar");
    test("&amp;&amp;&amp;", "&&&");
    test("&amp;&amp;&amp; ", "&&& ");
    test(" &amp;&amp;&amp;", " &&&");
    test("&amp;", "&");
    test("&amp;", "&amp;");
    test("&amp;&amp;", "&amp;&amp;");
    test("&amp;&amp;&amp;", "&amp;&&amp;");
    test("test", "test");
    test("", "");
    test("testtesttest&amp;", "testtesttest&");
0 голосов
/ 03 января 2011

Чтобы избежать чтения всех данных в ОЗУ, вы можете реализовать FilterInputStream (вам придется переопределить и read(), и read(byte[],int,int) и как-то посмотреть на буферизацию этих дополнительных байтов. Это не приведет к сокращению кода.


Реальное решение состоит в том, чтобы исправить неверный источник данных (и если вы собираетесь автоматизировать это, вам нужно взглянуть на написание собственного синтаксического анализатора XML).

Ваш подходимеет несколько недостатков.

  • Результат String.getBytes() зависит от системы, это также операция транскодирования, которая может не быть симметричной с тем, что делает StringUtil.toString - кодировки по умолчанию во многих системах lossy . Вы должны выполнить транскодирование, используя XML-кодировку документа .
  • Глобальный поиск и замена, подобный этому, может повредить ваш документ - амперсанды могут существовать в CDATA , объекты и объявления объектов .
...