/*
* 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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.logging.Logger;
import org.opensolaris.opengrok.analysis.FileAnalyzer.Genre;
/**
* The header of a crossfile.
*
* @author Jens Elkner
* @version $Revision$
*/
public class XrefHeader {
private static final Logger logger = Logger.getLogger(XrefHeader.class.getName());
/** the default format version of a crossfile */
public static int VERSION = 1;
private static final byte[] MAGIC = { 'O', 'p', 'E', 'n', 'G', 'r', 'O', 'k' };
/** the size of the header in bytes */
public static int HEADER_SZ = 32;
private int version;
private int lines;
private long chars;
private boolean compressed;
private Genre genre;
/**
* Create a new default header for a crossfile of the given genre. Data
* section is assumed to be uncompressed and line and char counts are set
* to -1.
* @param genre genre of the file.
* @see XrefHeader#XrefHeader(Genre, boolean)
*/
public XrefHeader(Genre genre) {
this(genre, false);
}
/**
* Create a new default header for a crossfile of the given genre and
* parameters. Line and char counts are set to -1.
* @param genre genre of the file.
* @param compressed {@code true} if data section is compressed
* @see XrefHeader#XrefHeader(Genre, boolean, int, long)
*/
public XrefHeader(Genre genre, boolean compressed) {
this(genre, compressed, -1, -1L);
}
/**
* Create a new default header for a crossfile of the given genre and
* parameters.
* @param genre genre of the data section.
* @param compressed {@code true} if data section is compressed
* @param lines number of lines represented by the data section. {@code -1}
* implies unknown.
* @param chars number of characters in the [compressed] data section.
*/
public XrefHeader(Genre genre, boolean compressed, int lines, long chars) {
if (genre == null) {
throw new IllegalArgumentException("genre null is not allowed");
}
this.genre = genre;
version = VERSION;
this.compressed = compressed;
this.lines = lines;
this.chars = chars;
}
/**
* Creates a ByteBuffer backed by an new byte array of {@link #HEADER_SZ}
* and fills in all relevant metadata.
*
* @return an unshared byte buffer
*/
public ByteBuffer getBytes() {
ByteBuffer bb = ByteBuffer.wrap(new byte[HEADER_SZ]);
bb.order(ByteOrder.BIG_ENDIAN);
bb.put(MAGIC);
bb.putInt(version);
bb.putInt(lines);
bb.putLong(chars);
bb.put((byte) genre.ordinal()); // or should we take the first char?
bb.put((byte) (compressed ? 1 : 0));
return bb;
}
/**
* Create a new instance by reading the required number of bytes
* from the given byte array starting at index 0.
* @param header byte array containing the header bytes.
* @throws IOException if the header is invalid.
*/
public XrefHeader(byte[] header) throws IOException {
ByteBuffer bb = ByteBuffer.wrap(header);
bb.order(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < MAGIC.length; i++) {
if (bb.get() != MAGIC[i]) {
throw new IOException("invalid header");
}
}
this.version = bb.getInt();
if (VERSION != version) {
logger.warning("Found xref file format version " + version
+ " this may lead to unexpected results (supported is "
+ VERSION + ").");
}
this.lines = bb.getInt();
this.chars = bb.getLong();
int g = bb.get();
Genre[] values = Genre.values();
if (g < 0 || g >= values.length) {
throw new IOException("invalid header");
}
genre = values[g];
this.compressed = bb.get() != 0;
}
/**
* Create a new instance by reading the appropriate number of header bytes
* from the given stream.
* @param in from where to read the header.
* @throws IOException if the header is invalid or other error occures when
* reading the stream.
*/
public XrefHeader(InputStream in) throws IOException {
this(readHeader(in));
}
private static final byte[] readHeader(InputStream in) throws IOException {
byte[] header = new byte[HEADER_SZ];
int count = 0;
while (count < header.length) {
int i = in.read(header, count, header.length - count);
if (i < 0) {
throw new IOException("invalid header");
}
count += i;
}
return header;
}
/**
* Write the header to the given stream.
* @param out where to write the header.
* @throws IOException
*/
public void write(OutputStream out) throws IOException {
out.write(getBytes().array());
}
/**
* Get the number of characters, which follow the header. NOTE: NOT the
* compressed size!
* @return number of characters in the data section. {@code -1} implies
* unknown.
*/
public long getSize() {
return chars;
}
/**
* Set the number of characters, which follow the header. NOTE: NOT the
* compressed size!
* @param size number of characters in the data section. {@code -1} implies
* unknown.
*/
public void setSize(long size) {
this.chars = size < 0 ? -1L : size;
}
/**
* Get the number of lines represented by the data section.
* @return number of lines. {@code -1} implies unknown.
*/
public int getLines() {
return lines;
}
/**
* Set the number of lines represented by the data section.
* @param lines number of lines. {@code -1} implies unknown.
*/
public void setLines(int lines) {
this.lines = lines < 0 ? -1 : lines;
}
/**
* Get the version of the header (file format).
* @return a number &gt; 0.
*/
public int getVersion() {
return version;
}
/**
* The genre to which the data section belongs to.
* @return a non-{@code null} value.
*/
public Genre getGenre() {
return genre;
}
/**
* Check, whether the data section of the file is compressed.
* @return {@code true} if compressed.
*/
public boolean isCompressed() {
return compressed;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "Header: Version=" + version + ", Genre=" + genre.name()
+ ", SourceLines=" + (lines < 0 ? "?" : Integer.toString(lines))
+ ", Characters=" + (chars < 0 ? "?" : Long.toString(chars));
}
}