package com.opensymphony.module.sitemesh; import com.opensymphony.module.sitemesh.html.util.StringSitemeshBuffer; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.*; /** * A fragment of a sitemesh buffer. This includes a start and a length, and may contain a list of deleted sections of * the buffer. */ public class SitemeshBufferFragment { private final SitemeshBuffer buffer; private final int start; private final int length; private final TreeMap<Integer, Integer> deletions; public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length) { this(buffer, start, length, new TreeMap<Integer, Integer>()); } /** * Create a sitemesh buffer fragment * * @param buffer The buffer that this is a fragment of * @param start The start of the fragment * @param length The length of the fragment * @param deletions Deleted parts of the fragment, as a map of positions to the length to be deleted. */ public SitemeshBufferFragment(SitemeshBuffer buffer, int start, int length, TreeMap<Integer, Integer> deletions) { this.buffer = buffer; this.start = start; this.length = length; this.deletions = deletions; } /** * Write the fragment to the given writer * * @param writer The writer to write the fragment to * @throws IOException If an error occured */ public void writeTo(Writer writer) throws IOException { int pos = start; for (Map.Entry<Integer, Integer> delete : deletions.entrySet()) { int deletePos = delete.getKey(); if (deletePos >= pos) { buffer.writeTo(writer, pos, deletePos - pos); } pos = Math.max(deletePos + delete.getValue(), start); } int remain = start + length - pos; if (remain >= 0) { buffer.writeTo(writer, pos, remain); } } /** * Get the total length of the fragment, taking deletions and chained buffers of the buffer * * @return The total length of the fragment */ public int getTotalLength() { int total = 0; int pos = start; for (Map.Entry<Integer, Integer> delete : deletions.entrySet()) { int deletePos = delete.getKey(); if (deletePos > pos) { total += buffer.getTotalLength(pos, deletePos - pos); } pos = deletePos + delete.getValue(); } int remain = start + length - pos; if (remain > 0) { total += buffer.getTotalLength(pos, remain); } return total; } public String getStringContent() { StringWriter writer = new StringWriter(); try { writeTo(writer); } catch (IOException e) { throw new RuntimeException("Exception writing to buffer", e); } return writer.toString(); } @Override public String toString() { return "SitemeshBufferFragment{" + // Here we generate our own ID, because if the underlying writer is a CharArrayWriter, we'll end up // with its entire contents, which we don't really want in this method. "buffer=" + buffer.getClass().getName() + "@" + Integer.toHexString(hashCode()) + ", start=" + start + ", length=" + length + ", deletions=" + deletions + '}'; } public int getStart() { return start; } public int getLength() { return length; } public static Builder builder() { return new Builder(); } public static Builder builder(SitemeshBufferFragment fragment) { return new Builder(fragment); } /** * A builder for fragments. */ public static class Builder { private DefaultSitemeshBuffer.Builder buffer; private int start; private int length; private final TreeMap<Integer, Integer> deletions; private Integer startDelete; private Builder() { this.deletions = new TreeMap<Integer, Integer>(); } private Builder(SitemeshBufferFragment fragment) { this.buffer = DefaultSitemeshBuffer.builder(fragment.buffer); this.start = fragment.start; this.length = fragment.length; this.deletions = new TreeMap<Integer, Integer>(fragment.deletions); } public Builder setStart(int start) { this.start = start; return this; } public Builder setLength(int length) { this.length = length; return this; } /** * Delete length characters from pos in this buffer fragment * * @param pos The position to delete from * @param length The number of characters to delete * @return The builder */ public Builder delete(int pos, int length) { this.deletions.put(pos, length); return this; } /** * Mark the start of the fragment * * @param pos The start of the fragment * @return The builder */ public Builder markStart(int pos) { this.start = pos; this.length = 0; return this; } /** * End the fragment * * @param pos The position of the end of the fragment * @return The builder */ public Builder end(int pos) { this.length = pos - this.start; return this; } /** * Mark the start of a deletion. * * @param pos The position to start deleting from * @return The builder * @throws IllegalStateException If markStartDelete() has already been called and endDelete() hasn't been called */ public Builder markStartDelete(int pos) { if (startDelete != null) { throw new IllegalStateException("Can't nested delete..."); } startDelete = pos; return this; } /** * End the current deletion * * @param pos The position to delete to * @return The builder * @throws IllegalStateException If markStartDelete() hasn't been called */ public Builder endDelete(int pos) { if (startDelete == null) { throw new IllegalStateException("Ending delete with no start delete..."); } delete(startDelete, pos - startDelete); startDelete = null; return this; } /** * Insert the given fragment to the given position * * @param position The position to insert the fragment to * @param fragment The fragment to insert * @return The builder */ public Builder insert(int position, SitemeshBufferFragment fragment) { buffer.insert(position, fragment); return this; } /** * Insert the given string fragment to the given position * * @param position The position to insert at * @param fragment The fragment to insert * @return The builder */ public Builder insert(int position, String fragment) { buffer.insert(position, StringSitemeshBuffer.createBufferFragment(fragment)); return this; } /** * Set the buffer. This resets both start and length to be that of the buffer. * * @param sitemeshBuffer The buffer to set. * @return The builder */ public Builder setBuffer(SitemeshBuffer sitemeshBuffer) { this.buffer = DefaultSitemeshBuffer.builder(sitemeshBuffer); this.start = 0; this.length = sitemeshBuffer.getBufferLength(); return this; } /** * Build the fragment * * @return The built fragment */ public SitemeshBufferFragment build() { return new SitemeshBufferFragment(buffer.build(), start, length, deletions); } } }