/* * 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 > 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)); } }