All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.atlassian.clover.registry.format.RegHeader Maven / Gradle / Ivy

Go to download

Clover's runtime library - required by code instrumented by Clover. Clover is an award-winning code coverage and testing tool for Java and Groovy. It integrates easily with Maven, Ant, Grails, Eclipse and IntelliJ IDEA as well as with continuous integration servers such as Bamboo, Jenkins or Hudson.

The newest version!
package com.atlassian.clover.registry.format;

import com.atlassian.clover.registry.CorruptedRegistryException;
import com.atlassian.clover.registry.IncompatibleRegistryFormatException;
import com.atlassian.clover.registry.RegistryFormatException;

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.zip.Adler32;

/**
 * Encapsulates the header of a Clover registry file. Great care has been taken
 * to ensure this class can be used with NIO and with old IO. Old IO usage
 * is necessary so that Java 1.4 is not required at runtime.
 */
public class RegHeader {
    public static final long REG_MAGIC = 0xCAFEFEED;
    public static final int REG_FORMAT_VERSION = 40502; /* A.BB.CC 4.5.2 update this value whenever format is changed */
    public static final int MAX_NAME_LENGTH = 64;
    /** Size in bytes of the header */
    public static final int SIZE = 52 + (MAX_NAME_LENGTH * 4);

    /** Mode: readonly / readwrite */
    private final RegAccessMode accessMode;
    /** Registry version */
    private final long version;
    /** The number of slots in the reg. Needed int the header to allow access to it at runtime */
    private final int slotCount;
    /** Where coverage data starts in the file */
    private final long coverageLocation;
    /** Where the most recent instr session starts in the file. */
    private final long lastSessionLocation;
    /** Name of the registry */
    private final String name;

    RegHeader(RegAccessMode accessMode, long version, int slotCount, long coverageLocation, long lastSessionLocation, String name) {
        this.accessMode = accessMode;
        this.version = version;
        this.slotCount = slotCount;
        this.coverageLocation = coverageLocation;
        this.lastSessionLocation = lastSessionLocation;
        this.name = name;
    }

    ///CLOVER:OFF
    public RegAccessMode getAccessMode() {
        return accessMode;
    }

    public int getSlotCount() {
        return slotCount;
    }

    public long getVersion() {
        return version;
    }

    public long getLastSessionLocation() {
        return lastSessionLocation;
    }

    public long getCoverageLocation() {
        return coverageLocation;
    }

    public String getName() {
        return name;
    }
    ///CLOVER:ON

    /**
     * Read the registry header.
     * Used at recording time by recorders to find the size of the data array
     * @param registryFile the registry file
     * @return the header of the file
     * @throws IOException if an unexpected IO problem occurs while reading
     * @throws RegistryFormatException if a known registry problem is encountered
     */
    public static RegHeader readFrom(File registryFile) throws IOException, RegistryFormatException {
        try (DataInputStream stream = new DataInputStream(Files.newInputStream(registryFile.toPath()))) {
            return readFrom(new StreamInputSource(registryFile.getAbsolutePath(), stream));
        } catch (EOFException e) {
            throw new CorruptedRegistryException(
                    "The Clover registry file \"" + registryFile.getAbsolutePath() + "\" is invalid (truncated header). Please regenerate.");
        }
    }

    /**
     * Read the registry header via a NIO FileChannel.
     * @param name the name of the registry file
     * @param channel the file channel from which to read
     * @return the header of the file
     * @throws IOException if an unexpected IO problem occurs while reading
     * @throws RegistryFormatException if a known registry problem is encountered
     */
    public static RegHeader readFrom(String name, FileChannel channel) throws IOException, RegistryFormatException {
        try {
            return readFrom(new BufferInputSource(name, BufferUtils.readFully(channel, ByteBuffer.allocate(SIZE))));
        } catch (BufferUnderflowException e) {
            throw new CorruptedRegistryException(
                "The Clover registry file \"" + name + "\" is invalid (truncated header). Please regenerate.");
        }
    }

    /**
     * Read header using an abstraction on the underlying data source. Necessary to avoid NIO references
     * in a 1.3 or lesser environment.
     */
    protected static RegHeader readFrom(HeaderInputSource dis) throws IOException, RegistryFormatException {
        final long magic = dis.getLong();
        if (REG_MAGIC != magic) {
            throw new CorruptedRegistryException(
                "File \"" + dis.getName() + "\" is not a valid Clover registry file (file magic number invalid - expected 0x" +
                Integer.toHexString((int)REG_MAGIC) + " but was 0x" + Integer.toHexString((int)magic) + "). Please regenerate.");
        }
        final int regFormat = dis.getInt();
        if (REG_FORMAT_VERSION != regFormat) {
            throw new IncompatibleRegistryFormatException(
                "Clover is no longer compatible with the registry file \"" + dis.getName() +
                "\" (format version " + regFormat + ", supported " + REG_FORMAT_VERSION + "). Please regenerate.");
        }

        final Adler32 checksum = new Adler32();
        final int mode = dis.getInt(checksum);
        final long version = dis.getLong(checksum);
        final int slotCount = dis.getInt(checksum);
        final long coverageLocation = dis.getLong(checksum);
        final long lastSessionLocation = dis.getLong(checksum);

        final char[] name = new char[MAX_NAME_LENGTH];
        for (int i = 0; i < name.length; i++) {
            name[i] = dis.getChar(checksum);
        }

        if (dis.getLong() != checksum.getValue()) {
            throw new CorruptedRegistryException(
                "Clover registry File \"" + dis.getName() + "\" may have been corrupted (header checksum incorrect). Please regenerate.");
        }

        return new RegHeader(RegAccessMode.getFor(mode), version, slotCount, coverageLocation, lastSessionLocation, new String(name).trim());
    }

    public void write(FileChannel channel) throws IOException {

        ByteBuffer buffer = ByteBuffer.allocate(SIZE);
        buffer.putLong(REG_MAGIC);                                             //8 bytes +
        buffer.putInt(REG_FORMAT_VERSION);                                     //4 bytes +

        Adler32 checksum = new Adler32();
        BufferUtils.putWithChecksum(buffer, accessMode.getValue(), checksum);  //4 bytes +
        BufferUtils.putWithChecksum(buffer, version, checksum);                //8 bytes +
        BufferUtils.putWithChecksum(buffer, slotCount, checksum);              //4 bytes +
        BufferUtils.putWithChecksum(buffer, coverageLocation, checksum);       //8 bytes +
        BufferUtils.putWithChecksum(buffer, lastSessionLocation, checksum);    //8 bytes +
                                                                               //---------
                                                                               //44 bytes +
        //Pad to 64 chars... or...                                             //128 bytes +
        final char[] chars = String.format("%-" + MAX_NAME_LENGTH + "." + MAX_NAME_LENGTH + "s", name).toCharArray();
        for (char oneChar : chars) {
            BufferUtils.putWithChecksum(buffer, oneChar, checksum);
        }

        buffer.putLong(checksum.getValue());                                   //8 bytes
                                                                               //---------
                                                                               //=180 bytes

        buffer.flip();

        BufferUtils.writeFully(channel, buffer);
    }

    /**
     * Abstraction to avoid NIO specific code when instrumented applications are running
     * yet allow shared behaviour across NIO and IO.
     **/
    private interface HeaderInputSource {
        String getName();
        long getLong() throws IOException;
        int getInt() throws IOException;
        long getLong(Adler32 checksum) throws IOException;
        int getInt(Adler32 checksum) throws IOException;
        char getChar(Adler32 checksum) throws IOException;
    }

    /** {@link HeaderInputSource} from a {@link ByteBuffer} */
    private static class BufferInputSource implements HeaderInputSource {
        private final String name;
        private final ByteBuffer buffer;

        private BufferInputSource(String name, ByteBuffer buffer) {
            this.name = name;
            this.buffer = buffer;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public long getLong() {
            return buffer.getLong();
        }

        @Override
        public int getInt() {
            return buffer.getInt();
        }

        @Override
        public long getLong(Adler32 checksum) {
            return BufferUtils.getLongWithChecksum(buffer, checksum);
        }

        @Override
        public int getInt(Adler32 checksum) {
            return BufferUtils.getIntWithChecksum(buffer, checksum);
        }

        @Override
        public char getChar(Adler32 checksum) {
            //Chars are stored/retrieved as ints because DataInputStream and (by default)
            //ByteBuffers disagree on representation of chars
            return (char)BufferUtils.getIntWithChecksum(buffer, checksum);
        }
    }

    /** {@link HeaderInputSource} from a {@link DataInputStream} */
    private static class StreamInputSource implements HeaderInputSource {
        private final DataInputStream dis;
        private final String name;

        private StreamInputSource(String name, DataInputStream dis) {
            this.name = name;
            this.dis = dis;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public long getLong() throws IOException {
            return dis.readLong();
        }

        @Override
        public int getInt() throws IOException {
            return dis.readInt();
        }

        @Override
        public long getLong(Adler32 checksum) throws IOException {
            final long result = dis.readLong();
            checksum.update((int)(result & 0xFFFFL));
            checksum.update((int)(result >> 32));
            return result;
        }

        @Override
        public int getInt(Adler32 checksum) throws IOException {
            final int result = dis.readInt();
            checksum.update(result);
            return result;
        }

        @Override
        public char getChar(Adler32 checksum) throws IOException {
            //Chars are stored/retrieved as ints because DataInputStream and ByteBuffers disagree
            //on representation of chars
            return (char)getInt(checksum);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy