/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* See LICENSE.txt included in this distribution for the specific
* language governing permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* Portions Copyright 2012 Jens Elkner.
*/
package org.opensolaris.opengrok.analysis;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.nio.channels.FileChannel;
import java.util.zip.GZIPOutputStream;
import org.opensolaris.opengrok.analysis.FileAnalyzer.Genre;
import org.opensolaris.opengrok.util.IOUtils;
/**
* An UTF-8 OutputStreamWriter used to write Xref files.
*
* @author Jens Elkner
* @version $Revision$
*/
public class XrefWriter extends Writer {
private long count;
private RandomFileOutputStream fos;
private Writer out;
private long mark = -1;
private XrefHeader header;
/**
* Creates an uncompressed xref output stream by creating/overwriting the
* file with the given name.
* @param name name of the file to create/overwrite.
* @param genre the genre of the data to be written.
* @throws IOException
*/
public XrefWriter(String name, Genre genre) throws IOException {
this(name, genre, true);
}
/**
* Creates an [un]compressed xref output stream by creating/overwriting the
* file with the given name.
* @param name name of the file to create/overwrite
* @param genre the genre of the data to be written.
* @param compress if {@code true} written data gets compressed.
* @throws IOException
*/
public XrefWriter(String name, Genre genre, boolean compress) throws IOException {
this(new File(name), genre, compress);
}
/**
* Creates an uncompressed xref output stream by creating/overwriting the
* given file.
*
* @param file file to write
* @param genre the genre of the data to be written.
* @throws IOException
*/
public XrefWriter(File file, Genre genre) throws IOException {
this(file, genre, false);
}
private class RandomFileOutputStream extends OutputStream {
RandomAccessFile raf;
public RandomFileOutputStream(File file) throws FileNotFoundException {
if (file == null) {
throw new IllegalArgumentException("null is not allowed");
}
raf = new RandomAccessFile(file, "rw");
}
@Override
public void write(int b) throws IOException {
raf.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
raf.write(b, off, len);
}
@Override
public void close() {
// prevent wrapper streams from closing it
}
}
/**
* Creates an [un]compressed xref output stream by creating/overwriting the
* given file.
*
* @param file file to write
* @param genre the genre of the data to be written.
* @param compress if {@code true} written data gets compressed.
* @throws IOException
* @throws FileNotFoundException
* @see FileOutputStream
*/
public XrefWriter(File file, Genre genre, boolean compress) throws IOException {
super(file);
fos = new RandomFileOutputStream(file);
header = new XrefHeader(genre, compress);
header.write(fos);
out = new OutputStreamWriter(compress
? new GZIPOutputStream(fos, 4096)
: new BufferedOutputStream(fos, 16384), "UTF-8");
}
/**
* Just wraps the given writer. {@link #close()} on this instance gets
* ignored and thus no header will be written or updated.
* @param out writer to wrap.
*/
public XrefWriter(Writer out) {
super(new Object());
this.out = out;
}
/**
* Get the number of bytes written to the output stream.
* @return a number >= 0
* @see FileChannel#position()
*/
public long getCount() {
return count;
}
/**
* Get the file this instance is writing to.
* @return {@code null} if this instance wraps another writer, the file
* used to create this instance otherwise.
*/
public File getFile() {
return fos != null ? (File) this.lock : null;
}
/**
* {@inheritDoc}
*/
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
out.write(cbuf, off, len);
count += len;
}
/**
* {@inheritDoc}
*/
@Override
public void flush() throws IOException {
out.flush();
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
if (fos != null) {
IOUtils.close(out); //flush and release wrapper stream resources
fos.raf.seek(0);
header.setSize(count);
fos.raf.write(header.getBytes().array());
IOUtils.close(fos.raf);
}
out = null;
}
/**
* Remember the number of characters currently written to the underlying
* output stream. NOTE: This method is unsynchronized and thus may return
* an unexpected value if characters are written concurrently!
* @return the value of {@link #getCount()}
*/
public long mark() {
mark = getCount();
return mark;
}
/**
* Get the number of characters written to the underlying
* output stream at the last time when {@link #mark()} was called.
* @return {@code -1} if {@code mark()} has never been called, otherwise
* the value the last {@code mark()} returned.
*/
public long getMark() {
return mark;
}
/**
* Set the number of lines written to this stream.
* @param lines number of lines writen.
*/
public void setLines(int lines) {
if (header != null) {
header.setLines(lines);
}
}
}