/**
   * Writes out the source map in the following format (line numbers are for reference only and are
   * not part of the format):
   *
   * <p>1. { 2. version: 3, 3. file: "out.js", 4. lineCount: 2, 5. sourceRoot: "", 6. sources:
   * ["foo.js", "bar.js"], 7. names: ["src", "maps", "are", "fun"], 8. mappings: "a;;abcde,abcd,a;"
   * 9. x_org_extension: value 10. }
   *
   * <p>Line 1: The entire file is a single JSON object Line 2: File revision (always the first
   * entry in the object) Line 3: The name of the file that this source map is associated with. Line
   * 4: The number of lines represented in the source map. Line 5: An optional source root, useful
   * for relocating source files on a server or removing repeated prefix values in the "sources"
   * entry. Line 6: A list of sources used by the "mappings" entry relative to the sourceRoot. Line
   * 7: A list of symbol names used by the "mapping" entry. This list may be incomplete. Line 8: The
   * mappings field. Line 9: Any custom field (extension).
   */
  @Override
  public void appendTo(Appendable out, String name) throws IOException {
    int maxLine = prepMappings() + 1;

    // Add the header fields.
    out.append("{\n");
    appendFirstField(out, "version", "3");
    appendField(out, "file", escapeString(name));
    appendField(out, "lineCount", String.valueOf(maxLine));

    // optional source root
    if (this.sourceRootPath != null && !this.sourceRootPath.isEmpty()) {
      appendField(out, "sourceRoot", escapeString(this.sourceRootPath));
    }

    // Add the mappings themselves.
    appendFieldStart(out, "mappings");
    // out.append("[");
    (new LineMapper(out, maxLine)).appendLineMappings();

    // out.append("]");
    appendFieldEnd(out);

    // Files names
    appendFieldStart(out, "sources");
    out.append("[");
    addSourceNameMap(out);
    out.append("]");
    appendFieldEnd(out);

    // Sources contents
    addSourcesContentMap(out);

    // Identifier names
    appendFieldStart(out, "names");
    out.append("[");
    addSymbolNameMap(out);
    out.append("]");
    appendFieldEnd(out);

    // Extensions, only if there is any
    for (String key : this.extensions.keySet()) {
      Object objValue = this.extensions.get(key);
      String value;
      if (objValue instanceof String) {
        value = escapeString((String) objValue); // escapes native String
      } else {
        value = objValue.toString();
      }
      appendField(out, key, value);
    }

    out.append("\n}\n");
  }
  /**
   * Appends the index source map to the given buffer.
   *
   * @param out The stream to which the map will be appended.
   * @param name The name of the generated source file that this source map represents.
   * @param sections An ordered list of map sections to include in the index.
   * @throws IOException
   */
  @Override
  public void appendIndexMapTo(Appendable out, String name, List<SourceMapSection> sections)
      throws IOException {
    // Add the header fields.
    out.append("{\n");
    appendFirstField(out, "version", "3");
    appendField(out, "file", escapeString(name));

    // Add the line character maps.
    appendFieldStart(out, "sections");
    out.append("[\n");
    boolean first = true;
    for (SourceMapSection section : sections) {
      if (first) {
        first = false;
      } else {
        out.append(",\n");
      }
      out.append("{\n");
      appendFirstField(out, "offset", offsetValue(section.getLine(), section.getColumn()));
      if (section.getSectionType() == SourceMapSection.SectionType.URL) {
        appendField(out, "url", escapeString(section.getSectionValue()));
      } else if (section.getSectionType() == SourceMapSection.SectionType.MAP) {
        appendField(out, "map", section.getSectionValue());
      } else {
        throw new IOException("Unexpected section type");
      }
      out.append("\n}");
    }

    out.append("\n]");
    appendFieldEnd(out);

    out.append("\n}\n");
  }
 private void addSourcesContentMap(Appendable out) throws IOException {
   boolean found = false;
   List<String> contents = new ArrayList<>();
   contents.addAll(Collections.nCopies(sourceFileMap.size(), ""));
   for (Map.Entry<String, String> entry : sourceFileContentMap.entrySet()) {
     Integer index = sourceFileMap.get(entry.getKey());
     if (index != null && index < contents.size()) {
       contents.set(index, entry.getValue());
       found = true;
     }
   }
   if (!found) {
     return;
   }
   appendFieldStart(out, "sourcesContent");
   out.append("[");
   for (int i = 0; i < contents.size(); i++) {
     if (i != 0) {
       out.append(",");
     }
     out.append(escapeString(contents.get(i)));
   }
   out.append("]");
   appendFieldEnd(out);
 }