/**
   * Reads next line from the input and:
   *
   * <ul>
   *   <li>Converts ascii-encoded \\uxxxx chars to normal characters.
   *   <li>Converts \r, \n and \t to CR, line feed and tab.
   *   <li>But! Keeps a backspace in '\ ', '\=', '\:' etc (non-trimmable space or
   *       non-key-value-breaking :-) equals).
   *       <ul>
   *         Change from BufferedReader to LinebreakPreservingReader was part of fix for bug 1462566
   */
  protected String getNextLine(LinebreakPreservingReader reader)
      throws IOException, TranslationException {
    String ascii = reader.readLine();
    if (ascii == null) {
      return null;
    }

    StringBuilder result = new StringBuilder();
    for (int cp, len = ascii.length(), i = 0; i < len; i += Character.charCount(cp)) {
      cp = ascii.codePointAt(i);
      if (cp == '\\' && ascii.codePointCount(i, len) > 1) {
        i += Character.charCount(cp);
        cp = ascii.codePointAt(i);
        if (cp != 'u') {
          if (cp == 'n') {
            cp = '\n';
          } else if (cp == 'r') {
            cp = '\r';
          } else if (cp == 't') {
            cp = '\t';
          } else {
            result.append('\\');
          }
        } else if (dontUnescapeULiterals) {
          // Put back the \ we swallowed
          result.append('\\');
        } else {
          // checking if the string is long enough
          if (ascii.codePointCount(i, len) < 1 + 4) {
            throw new TranslationException(OStrings.getString("RBFH_ERROR_ILLEGAL_U_SEQUENCE"));
          }
          int uStart = ascii.offsetByCodePoints(i, 1);
          int uEnd = ascii.offsetByCodePoints(uStart, 4);
          String uStr = ascii.substring(uStart, uEnd);
          try {
            cp = Integer.parseInt(uStr, 16);
            if (!Character.isValidCodePoint(cp)) {
              throw new TranslationException(OStrings.getString("RBFH_ERROR_ILLEGAL_U_SEQUENCE"));
            }
            i = uEnd - Character.charCount(cp);
          } catch (NumberFormatException ex) {
            throw new TranslationException(OStrings.getString("RBFH_ERROR_ILLEGAL_U_SEQUENCE"), ex);
          }
        }
      }
      result.appendCodePoint(cp);
    }

    return result.toString();
  }
  /** Doing the processing of the file... */
  @Override
  public void processFile(BufferedReader reader, BufferedWriter outfile, FilterContext fc)
      throws IOException, TranslationException {
    LinebreakPreservingReader lbpr = new LinebreakPreservingReader(reader); // fix for bug 1462566
    String str;
    // Support to show the comments (localization notes) into the Comments panel
    String comments;
    boolean noi18n = false;

    // Parameter in the options of filter to customize the target file
    removeStringsUntranslated =
        processOptions != null
            && "true".equalsIgnoreCase(processOptions.get(OPTION_REMOVE_STRINGS_UNTRANSLATED));

    // Parameter in the options of filter to customize the behavior of the filter
    dontUnescapeULiterals =
        processOptions != null
            && "true".equalsIgnoreCase(processOptions.get(OPTION_DONT_UNESCAPE_U_LITERALS));

    // Initialize the comments
    comments = null;
    while ((str = getNextLine(lbpr)) != null) {

      // Variable to check if a segment is translated
      boolean translatedSegment = true;

      String trimmed = str.trim();

      // skipping empty strings
      if (trimmed.isEmpty()) {
        outfile.write(str + lbpr.getLinebreak());
        // Delete the comments
        comments = null;
        continue;
      }

      // skipping comments
      int firstCp = trimmed.codePointAt(0);
      if (firstCp == '#' || firstCp == '!') {
        outfile.write(toAscii(str, false) + lbpr.getLinebreak());
        // Save the comments
        comments = (comments == null ? str : comments + "\n" + str);
        // checking if the next string shouldn't be internationalized
        if (trimmed.indexOf("NOI18N") >= 0) {
          noi18n = true;
        }
        continue;
      }

      // reading the glued lines
      while (str.codePointBefore(str.length()) == '\\') {
        String next = getNextLine(lbpr);
        if (next == null) {
          next = "";
        }
        // gluing this line (w/o '\' on this line)
        // with next line (w/o leading spaces)
        str = str.substring(0, str.offsetByCodePoints(str.length(), -1)) + leftTrim(next);
      }

      // key=value pairs
      int equalsPos = searchEquals(str);

      // writing out key
      String key;
      if (equalsPos >= 0) {
        key = str.substring(0, equalsPos).trim();
      } else {
        key = str.trim();
      }
      key = removeExtraSlashes(key);
      // writing segment is delayed until verifying that the translation was made
      // outfile.write(toAscii(key, true));

      // advance if there're spaces or tabs after =
      if (equalsPos >= 0) {
        int equalsEnd = str.offsetByCodePoints(equalsPos, 1);
        while (equalsEnd < str.length()) {
          int cp = str.codePointAt(equalsEnd);
          if (cp != ' ' && cp != '\t') {
            break;
          }
          equalsEnd += Character.charCount(cp);
        }
        String equals = str.substring(equalsPos, equalsEnd);
        // writing segment is delayed until verifying that the translation was made
        // outfile.write(equals);

        // value, if any
        String value;
        if (equalsEnd < str.length()) {
          value = removeExtraSlashes(str.substring(equalsEnd));
        } else {
          value = "";
        }

        if (noi18n) {
          // if we don't need to internationalize
          outfile.write(toAscii(value, false));
          noi18n = false;
        } else {
          value = value.replaceAll("\\n\\n", "\n \n");
          // If there is a comment, show it into the Comments panel
          String trans = process(key, value, comments);
          // Delete the comments
          comments = null;
          // Check if the segment is not translated
          if ("--untranslated_yet--".equals(trans)) {
            translatedSegment = false;
            trans = value;
          }
          trans = trans.replaceAll("\\n\\s\\n", "\n\n");
          trans = toAscii(trans, false);
          if (!trans.isEmpty() && trans.codePointAt(0) == ' ') {
            trans = '\\' + trans;
          }
          // Non-translated segments are written based on the filter options
          if (translatedSegment == true || removeStringsUntranslated == false) {
            outfile.write(toAscii(key, true));
            outfile.write(equals);
            outfile.write(trans);
            outfile.write(lbpr.getLinebreak()); // fix for bug 1462566
          }
        }
      }
      // This line of code is moved up to avoid blank lines
      // outfile.write(lbpr.getLinebreak()); // fix for bug 1462566
    }
  }