<commit_before>package com.connorhaigh.javavpk.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;

import com.connorhaigh.javavpk.exceptions.ArchiveException;

public class Archive 
{
	/**
	 * Create a new VPK archive.
	 * @param file the archive file
	 */
	public Archive(File file)
	{
		this.file = file;
		
		this.signature = 0;
		this.version = 0;
		this.treeLength = 0;
		this.headerLength = 0;
		
		this.entries = new ArrayList<Entry>();
	}
	
	/**
	 * Load the raw data from file to this archive.
	 * @throws IOException if the archive could not be read
	 * @throws ArchiveException if a general archive exception occurs
	 */
	public void load() throws IOException, ArchiveException
	{
		try (FileInputStream fileInputStream = new FileInputStream(this.file))
		{
			//read header
			this.signature = this.readUnsignedInt(fileInputStream);
			this.version = this.readUnsignedInt(fileInputStream);
			this.treeLength = this.readUnsignedInt(fileInputStream);
			this.headerLength = 28;
			
			//check signature and version
			if (this.signature != Archive.SIGNATURE)
				throw new ArchiveException("Invalid signature");
			if (this.version != Archive.VERSION)
				throw new ArchiveException("Unsupported version");
			
			//miscellaneous
			//these parts of the header serve no purpose
			this.readUnsignedInt(fileInputStream);
			this.readUnsignedInt(fileInputStream);
			this.readUnsignedInt(fileInputStream);
			this.readUnsignedInt(fileInputStream);
			
			//extension loop
			while (true)
			{
				//get extension
				String extension = this.readString(fileInputStream);
				if (extension.isEmpty())
					break;
				
				//path loop
				while (true)
				{
					//get path
					String path = this.readString(fileInputStream);
					if (path.isEmpty())
						break;
					
					//filename loop
					while (true)
					{
						//get filename
						String filename = this.readString(fileInputStream);
						if (filename.isEmpty())
							break;
						
						//read data
						int crc = this.readUnsignedInt(fileInputStream);
						short preloadSize = this.readUnsignedShort(fileInputStream);
						short archiveIndex = this.readUnsignedShort(fileInputStream);
						int entryOffset = this.readUnsignedInt(fileInputStream);
						int entryLength = this.readUnsignedInt(fileInputStream);
						short terminator = this.readUnsignedShort(fileInputStream);
						
						//check preload data
						byte[] preloadData = null;
						if (preloadSize > 0)
						{
							//read preload data
							preloadData = new byte[preloadSize];
							fileInputStream.read(preloadData);
						}
							
						//create entry
						Entry entry = new Entry(this, archiveIndex, preloadData, extension, path, filename, crc, entryOffset, entryLength, terminator);
						this.entries.add(entry);
					}
				}
			}
		}
		catch (Exception ex)
		{
			//error
			throw ex;
		}
	}
	
	/**
	 * Reads a file input stream character by character until a null terminator is reached.
	 * @param fileInputStream the file input stream to read
	 * @return the assembled string
	 * @throws IOException if the stream could not be read
	 */
	private String readString(FileInputStream fileInputStream) throws IOException
	{
		//builder
		StringBuilder stringBuilder = new StringBuilder();
		
		//read
		int currentChar = 0;
		while ((currentChar = fileInputStream.read()) != 0x0)
			stringBuilder.append((char) currentChar);
		
		return stringBuilder.toString();
	}
	
	/**
	 * Reads an unsigned integer from a file input stream.
	 * @param fileInputStream the file input stream to read
	 * @return the unsigned integer
	 * @throws IOException if the stream could not be read
	 */
	private int readUnsignedInt(FileInputStream fileInputStream) throws IOException
	{
		//byte array<commit_msg>Added methods for unique entry retrieval.<commit_after>package com.connorhaigh.javavpk.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;

import com.connorhaigh.javavpk.exceptions.ArchiveException;

public class Archive 
{
	/**
	 * Create a new VPK archive.
	 * @param file the archive file
	 */
	public Archive(File file)
	{
		this.file = file;
		
		this.signature = 0;
		this.version = 0;
		this.treeLength = 0;
		this.headerLength = 0;
		
		this.entries = new ArrayList<Entry>();
	}
	
	/**
	 * Load the raw data from file to this archive.
	 * @throws IOException if the archive could not be read
	 * @throws ArchiveException if a general archive exception occurs
	 */
	public void load() throws IOException, ArchiveException
	{
		try (FileInputStream fileInputStream = new FileInputStream(this.file))
		{
			//read header
			this.signature = this.readUnsignedInt(fileInputStream);
			this.version = this.readUnsignedInt(fileInputStream);
			this.treeLength = this.readUnsignedInt(fileInputStream);
			this.headerLength = 28;
			
			//check signature and version
			if (this.signature != Archive.SIGNATURE)
				throw new ArchiveException("Invalid signature");
			if (this.version != Archive.VERSION)
				throw new ArchiveException("Unsupported version");
			
			//miscellaneous
			//these parts of the header serve no purpose
			this.readUnsignedInt(fileInputStream);
			this.readUnsignedInt(fileInputStream);
			this.readUnsignedInt(fileInputStream);
			this.readUnsignedInt(fileInputStream);
			
			//extension loop
			while (true)
			{
				//get extension
				String extension = this.readString(fileInputStream);
				if (extension.isEmpty())
					break;
				
				//path loop
				while (true)
				{
					//get path
					String path = this.readString(fileInputStream);
					if (path.isEmpty())
						break;
					
					//filename loop
					while (true)
					{
						//get filename
						String filename = this.readString(fileInputStream);
						if (filename.isEmpty())
							break;
						
						//read data
						int crc = this.readUnsignedInt(fileInputStream);
						short preloadSize = this.readUnsignedShort(fileInputStream);
						short archiveIndex = this.readUnsignedShort(fileInputStream);
						int entryOffset = this.readUnsignedInt(fileInputStream);
						int entryLength = this.readUnsignedInt(fileInputStream);
						short terminator = this.readUnsignedShort(fileInputStream);
						
						//check preload data
						byte[] preloadData = null;
						if (preloadSize > 0)
						{
							//read preload data
							preloadData = new byte[preloadSize];
							fileInputStream.read(preloadData);
						}
							
						//create entry
						Entry entry = new Entry(this, archiveIndex, preloadData, extension, path, filename, crc, entryOffset, entryLength, terminator);
						this.entries.add(entry);
					}
				}
			}
		}
		catch (Exception ex)
		{
			//error
			throw ex;
		}
	}
	
	/**
	 * Returns a list of entries within the specified path.
	 * @param path the path to look in
	 * @return the list of entries
	 */
	public ArrayList<Entry> getEntriesIn(String path)
	{
		//results
		ArrayList<Entry> entries = new ArrayList<Entry>();
		
		//loop and check
		for (Entry entry : this.entries)
			if (entry.getPath().startsWith(path))
				entries.add(entry);
		
		return entries;
	}
	
	/**
	 * Returns an entry with the specified full name (path, filename and extension) in this archive.
	 * @param path the full name of the entry
	 * @return the entry, or null
	 */
	public Entry getEntry(String fullName)
	{
		//loop and check
		for (Entry entry : this.entries)
			if (entry.getFullName().equals(fullName))
				return entry;
		
		return null;
	}
	
	/**
	 * Reads a file input stream character by character until a null terminator is reached.
	 * @param fileInputStream the file input stream to read
	 * @return the assembled string
	 * @throws IOException if the stream could not be read
	 */
	private String readString(FileInputStream fileInputStream) throws IOException
	{
		//builder
		StringBuilder stringBuilder = new StringBuilder();
		
		//read
		int currentChar = 0;
		while ((currentChar = fileInputStream.read()) != 0x0)
			stringBuilder.append((char) currentChar);
		
		return stringBuilder.toString();
	}
	
	/**
	 * Reads an unsigned integer from a file input stream.
	 * @param fileInputStream the file input stream to read
	 * @return the unsigned integer
	 * @throws IOException if the stream could not be read
	 */
	private int readUnsignedInt(FileInputStream fileInputStream) throws IOException
	{
		//byte array
{'fraction_non_alphanumeric': 0.093841642228739, 'fraction_numerical': 0.002346041055718475, 'mean_word_length': 3.1651196873473375, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6531742', 'n_tokens_mistral': 3031, 'n_tokens_neox': 2331, 'n_words': 717}