/* * Copyright (C) 2014 James Lawrence. * * This file is part of LibLab. * * LibLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.sqrt.liblab; import com.sqrt.liblab.codec.CodecMapper; import com.sqrt.liblab.codec.EntryCodec; import com.sqrt.liblab.entry.LabEntry; import com.sqrt.liblab.io.DataSource; import com.sqrt.liblab.io.DiskDataSource; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; /** * A LabFile is a container for LabEntry's */ public class LabFile { /** * The collection that this LabFile belongs to */ public final LabCollection container; /** * The names and data for the entries of this LabFile */ public final List<DataSource> entries = new LinkedList<DataSource>(); private int version = 256; private String name; private File path; private RandomAccessFile source; LabFile(LabCollection container) { this.container = container; name = "New"; } LabFile(LabCollection container, File path) throws IOException { this(container); this.path = path; name = path.getName().toUpperCase(); try { source = new RandomAccessFile(path, "rw"); } catch (IOException ioe) { source = new RandomAccessFile(path, "r"); } int magic; if ((magic = source.readInt()) != (('L' << 24) | ('A' << 16) | ('B' << 8) | ('N'))) throw new IOException(String.format("Invalid LAB file, magic: %08x", magic)); version = source.readInt(); // Always 256? int entries = Integer.reverseBytes(source.readInt()); // should be uint but the chances of there being over 2147483647 entries is, I assume: slim int stringTableOffset = 16 * (entries + 1); for (int i = 0; i < entries; i++) { source.seek((i+1) * 16); // position... int nameOff = Integer.reverseBytes(source.readInt()); int start = Integer.reverseBytes(source.readInt()); int size = Integer.reverseBytes(source.readInt()); // 4 bytes of 0 // nul terminated string StringBuilder sb = new StringBuilder(); source.seek(stringTableOffset + nameOff); int ch; while ((ch = source.read()) != -1) { if (ch == 0) break; sb.append((char) ch); } this.entries.add(new DiskDataSource(this, sb.toString(), source, start, size)); } // Sort them for convenience... Collections.sort(this.entries, new Comparator<DataSource>() { public int compare(DataSource o1, DataSource o2) { return o1.getName().compareToIgnoreCase(o2.getName()); } }); } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * Returns all the LabEntry's contained in this LabFile that have a model of the specified type * @param type the type of the model * @return a list containing the results */ public List<DataSource> findByType(Class<? extends LabEntry> type) { List<DataSource> res = new LinkedList<DataSource>(); for (DataSource edp : entries) { EntryCodec<?> codec = CodecMapper.codecForProvider(edp); if (codec == null || codec.getEntryClass() != type) continue; res.add(edp); } if (res.isEmpty()) return null; return res; } /** * Fins the first LabEntry with the specified name * @param name the name to search for (case insensitive) * @return the entry or null if one was not found * @throws IOException */ public LabEntry findByName(String name) throws IOException { for (DataSource edp : entries) { if (edp.getName().equalsIgnoreCase(name)) { EntryCodec c = CodecMapper.codecForProvider(edp); if(c == null) continue; edp.position(0); return c.read(edp); } } return null; } public String toString() { return name; } public void save(File file) throws IOException { // Todo: save! RandomAccessFile raf = new RandomAccessFile(file, "rw"); raf.writeInt((('L' << 24) | ('A' << 16) | ('B' << 8) | ('N'))); raf.writeInt(version); raf.writeInt(Integer.reverseBytes(entries.size())); // Build string table first // This means we have to iterate twice, but we don't have to buffer all file data in memory ByteArrayOutputStream nameBuffer = new ByteArrayOutputStream(); int[] nameIndices = new int[entries.size()]; int stringTableSize = 0; for(int i = 0; i < entries.size(); i++) { nameIndices[i] = stringTableSize; byte[] nameData = entries.get(i).getName().getBytes(); nameBuffer.write(nameData, 0, nameData.length); nameBuffer.write(0); stringTableSize += nameData.length + 1; nameData = null; } raf.writeInt(stringTableSize); int stringTableOff = ((entries.size()+1) * 16); int dataOff = stringTableOff + stringTableSize; byte[] buf = new byte[5000]; for (int i = 0; i < entries.size(); i++) { raf.seek((i+1) * 16); DataSource d = entries.get(i); raf.writeInt(Integer.reverseBytes(nameIndices[i])); raf.writeInt(Integer.reverseBytes(dataOff)); raf.writeInt(Integer.reverseBytes((int) d.length())); raf.writeInt(0); // Write data raf.seek(dataOff); d.position(0); while(d.remaining() > 0) { int read = (int) Math.min(d.remaining(), buf.length); d.get(buf, 0, read); raf.write(buf, 0, read); dataOff += read; } } // Write the string table raf.seek(stringTableOff); raf.write(nameBuffer.toByteArray()); raf.close(); } }