Java tutorial
/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.model; import hudson.util.ByteBuffer; import hudson.util.CharSpool; import hudson.util.LineEndNormalizingWriter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.framework.io.WriterOutputStream; import org.apache.commons.io.output.CountingOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.io.Writer; import java.io.Reader; import java.io.InputStreamReader; /** * Represents a large text data. * * <p> * This class defines methods for handling progressive text update. * * @author Kohsuke Kawaguchi * @deprecated moved to stapler, as of Hudson 1.220 */ public class LargeText { /** * Represents the data source of this text. */ private interface Source { Session open() throws IOException; long length(); boolean exists(); } private final Source source; private volatile boolean completed; public LargeText(final File file, boolean completed) { this.source = new Source() { public Session open() throws IOException { return new FileSession(file); } public long length() { return file.length(); } public boolean exists() { return file.exists(); } }; this.completed = completed; } public LargeText(final ByteBuffer memory, boolean completed) { this.source = new Source() { public Session open() throws IOException { return new BufferSession(memory); } public long length() { return memory.length(); } public boolean exists() { return true; } }; this.completed = completed; } public void markAsComplete() { completed = true; } public boolean isComplete() { return completed; } /** * Returns {@link Reader} for reading the raw bytes. */ public Reader readAll() throws IOException { return new InputStreamReader(new InputStream() { final Session session = source.open(); public int read() throws IOException { byte[] buf = new byte[1]; int n = session.read(buf); if (n == 1) return buf[0]; else return -1; // EOF } public int read(byte[] buf, int off, int len) throws IOException { return session.read(buf, off, len); } public void close() throws IOException { session.close(); } }); } /** * Writes the tail portion of the file to the {@link Writer}. * * <p> * The text file is assumed to be in the system default encoding. * * @param start * The byte offset in the input file where the write operation starts. * * @return * if the file is still being written, this method writes the file * until the last newline character and returns the offset to start * the next write operation. */ public long writeLogTo(long start, Writer w) throws IOException { CountingOutputStream os = new CountingOutputStream(new WriterOutputStream(w)); Session f = source.open(); f.skip(start); if (completed) { // write everything till EOF byte[] buf = new byte[1024]; int sz; while ((sz = f.read(buf)) >= 0) os.write(buf, 0, sz); } else { ByteBuf buf = new ByteBuf(null, f); HeadMark head = new HeadMark(buf); TailMark tail = new TailMark(buf); while (tail.moveToNextLine(f)) { head.moveTo(tail, os); } head.finish(os); } f.close(); os.flush(); return os.getCount() + start; } /** * Implements the progressive text handling. * This method is used as a "web method" with progressiveText.jelly. */ public void doProgressText(StaplerRequest req, StaplerResponse rsp) throws IOException { rsp.setContentType("text/plain"); rsp.setCharacterEncoding("UTF-8"); rsp.setStatus(HttpServletResponse.SC_OK); if (!source.exists()) { // file doesn't exist yet rsp.addHeader("X-Text-Size", "0"); rsp.addHeader("X-More-Data", "true"); return; } long start = 0; String s = req.getParameter("start"); if (s != null) start = Long.parseLong(s); if (source.length() < start) start = 0; // text rolled over CharSpool spool = new CharSpool(); long r = writeLogTo(start, spool); rsp.addHeader("X-Text-Size", String.valueOf(r)); if (!completed) rsp.addHeader("X-More-Data", "true"); // when sending big text, try compression. don't bother if it's small Writer w; if (r - start > 4096) w = rsp.getCompressedWriter(req); else w = rsp.getWriter(); spool.writeTo(new LineEndNormalizingWriter(w)); w.close(); } /** * Points to a byte in the buffer. */ private static class Mark { protected ByteBuf buf; protected int pos; public Mark(ByteBuf buf) { this.buf = buf; } } /** * Points to the start of the region that's not committed * to the output yet. */ private static final class HeadMark extends Mark { public HeadMark(ByteBuf buf) { super(buf); } /** * Moves this mark to 'that' mark, and writes the data * to {@link OutputStream} if necessary. */ void moveTo(Mark that, OutputStream os) throws IOException { while (this.buf != that.buf) { os.write(buf.buf, 0, buf.size); buf = buf.next; pos = 0; } this.pos = that.pos; } void finish(OutputStream os) throws IOException { os.write(buf.buf, 0, pos); } } /** * Points to the end of the region. */ private static final class TailMark extends Mark { public TailMark(ByteBuf buf) { super(buf); } boolean moveToNextLine(Session f) throws IOException { while (true) { while (pos == buf.size) { if (!buf.isFull()) { // read until EOF return false; } else { // read into the next buffer buf = new ByteBuf(buf, f); pos = 0; } } byte b = buf.buf[pos++]; if (b == '\r' || b == '\n') return true; } } } private static final class ByteBuf { private final byte[] buf = new byte[1024]; private int size = 0; private ByteBuf next; public ByteBuf(ByteBuf previous, Session f) throws IOException { if (previous != null) { assert previous.next == null; previous.next = this; } while (!this.isFull()) { int chunk = f.read(buf, size, buf.length - size); if (chunk == -1) return; size += chunk; } } public boolean isFull() { return buf.length == size; } } /** * Represents the read session of the {@link Source}. * Methods generally follow the contracts of {@link InputStream}. */ private interface Session { void close() throws IOException; void skip(long start) throws IOException; int read(byte[] buf) throws IOException; int read(byte[] buf, int offset, int length) throws IOException; } /** * {@link Session} implementation over {@link RandomAccessFile}. */ private static final class FileSession implements Session { private final RandomAccessFile file; public FileSession(File file) throws IOException { this.file = new RandomAccessFile(file, "r"); } public void close() throws IOException { file.close(); } public void skip(long start) throws IOException { file.seek(file.getFilePointer() + start); } public int read(byte[] buf) throws IOException { return file.read(buf); } public int read(byte[] buf, int offset, int length) throws IOException { return file.read(buf, offset, length); } } /** * {@link Session} implementation over {@link ByteBuffer}. */ private static final class BufferSession implements Session { private final InputStream in; public BufferSession(ByteBuffer buf) { this.in = buf.newInputStream(); } public void close() throws IOException { in.close(); } public void skip(long n) throws IOException { while (n > 0) n -= in.skip(n); } public int read(byte[] buf) throws IOException { return in.read(buf); } public int read(byte[] buf, int offset, int length) throws IOException { return in.read(buf, offset, length); } } }