/*******************************************************************************
* Copyright (c) MOBAC developers
*
* This program 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 2 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 mobac.program.atlascreators.impl.rungps;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import mobac.utilities.Charsets;
import mobac.utilities.Utilities;
/**
*
* @author Tom Henne, eSymetric
*/
public class RunGPSAtlasFile {
public final static String SUFFIX = ".ratlas";
RandomAccessFile raf;
RunGPSAtlasHeader rah = new RunGPSAtlasHeader();
File cacheFile;
OutputStream cacheOutStream = null;
long positionInCacheFile = 0L;
int offset = 0;
int indexLength = 0;
public class RAINode {
RAINode() {
key = -1;
value = -1L;
next = -1;
child = -1;
index = -1;
}
int key;
long value;
int next;
int child;
int index;
void readFrom(int index) throws IOException {
this.index = index;
raf.seek(index);
key = raf.readInt();
value = raf.readLong();
next = raf.readInt();
child = raf.readInt();
}
void writeNew() throws IOException {
raf.seek(indexLength); // go to end
raf.writeInt(key);
raf.writeLong(value);
raf.writeInt(next);
raf.writeInt(child);
this.index = indexLength;
indexLength += 20;
}
void write() throws IOException {
raf.seek(index + 4);
raf.writeLong(value);
raf.writeInt(next);
raf.writeInt(child);
}
String listAll() throws IOException {
return listAll("");
}
String listAll(String path) throws IOException {
List<RAINode> children = getChildren();
StringBuilder sb = new StringBuilder();
if (key != -1) { // not for root
path += "" + key + "/";
if (value != -1L) {
sb.append(path).append('=').append(value).append("\n");
}
}
for (RAINode childNode : children) {
sb.append(childNode.listAll(path));
}
return sb.toString();
}
List<RAINode> getChildren() throws IOException {
// currentIndexPos is now at fathers node index pos or -1
List<RAINode> children = new ArrayList<RAINode>();
int indexPos = child;
for (;;) {
if (indexPos == -1) {
break;
}
RAINode n = new RAINode();
n.readFrom(indexPos);
children.add(n);
indexPos = n.next;
}
return children;
}
RAINode getChildNode(int key, boolean createNew) throws IOException {
// currentIndexPos is now at fathers node index pos or -1
// check if father has a child
if (this.child == -1) {
RAINode newNode = new RAINode();
newNode.key = key;
newNode.writeNew();
this.child = newNode.index;
this.write();
return newNode;
}
// find node on this level
RAINode n = new RAINode();
int currentIndexPos = this.child;
for (;;) {
if (currentIndexPos == -1) {
break;
}
n.readFrom(currentIndexPos);
if (n.key == key) {
return n;
}
currentIndexPos = n.next;
}
if (!createNew) {
return null;
}
RAINode newNode = new RAINode();
newNode.key = key;
newNode.writeNew();
n.next = newNode.index;
n.write();
return newNode;
}
public int getSize() {
return indexLength;
}
public RandomAccessFile getRandomAccessFile() {
return raf;
}
RAINode getChildNode(List<Integer> hierarchy) throws IOException {
if (hierarchy.isEmpty()) {
return this;
}
int key_ = hierarchy.get(0);
hierarchy.remove(0);
RAINode childNode = getChildNode(key_, false);
if (childNode == null) {
return null;
} else {
return childNode.getChildNode(hierarchy);
}
}
}
public boolean readSuccess() {
return rah.readSuccess();
}
public class RunGPSAtlasHeader {
public final static long FILETYPE_KEY = 8723487877262773L;
public final static int HEADER_SIZE = 128; // bytes
public final static int FILE_VERSION = 3;
int indexSize; // bytes
public void writeHeader() throws IOException {
raf.seek(0);
raf.writeLong(FILETYPE_KEY);
raf.writeInt(FILE_VERSION);
raf.writeInt(indexSize);
}
public boolean readHeader() throws IOException {
if (raf.readLong() != FILETYPE_KEY) {
return false;
}
if (raf.readInt() != FILE_VERSION) {
return false;
}
indexSize = raf.readInt();
return true;
}
public boolean readSuccess() {
return indexSize > 0;
}
}
public RunGPSAtlasFile(String filePath, boolean write) throws IOException {
this.offset = RunGPSAtlasHeader.HEADER_SIZE;
if (write) {
cacheFile = new File(filePath + ".cac");
cacheOutStream = new BufferedOutputStream(new FileOutputStream(cacheFile), 8216);
raf = new RandomAccessFile(filePath, "rw");
raf.setLength(0); // delete existing content
indexLength = offset;
RAINode root = new RAINode();
root.writeNew();
} else {
raf = new RandomAccessFile(filePath, "r");
rah.readHeader();
}
}
public RAINode getRootNode() throws IOException {
RAINode root = new RAINode();
root.readFrom(offset);
return root;
}
public String listAll() throws IOException {
return getRootNode().listAll();
}
public void close() throws IOException {
if (cacheOutStream != null) {
cacheOutStream.close();
}
if (raf != null) {
raf.close();
}
}
public void finishArchive() throws IOException {
cacheOutStream.close();
cacheOutStream = null;
FileInputStream fis = new FileInputStream(cacheFile);
rah.indexSize = indexLength - RunGPSAtlasHeader.HEADER_SIZE;
rah.writeHeader();
byte[] buf = new byte[4096];
raf.seek(indexLength);
for (;;) {
int l = fis.read(buf);
if (l <= 0) {
break;
}
raf.write(buf, 0, l);
}
fis.close();
Utilities.deleteFile(cacheFile);
close();
}
public void addData(List<Integer> hierarchy, byte[] data) throws IOException {
addData(hierarchy, data, data.length);
}
public void addData(List<Integer> hierarchy, byte[] data, int length) throws IOException {
cacheOutStream.write(data, 0, length);
List<Integer> posHierarchy = new ArrayList<Integer>();
posHierarchy.addAll(hierarchy);
posHierarchy.add(1); // position
setValue(posHierarchy, positionInCacheFile);
List<Integer> lenHierarchy = new ArrayList<Integer>();
lenHierarchy.addAll(hierarchy);
lenHierarchy.add(2); // size
setValue(lenHierarchy, length);
positionInCacheFile += length;
}
public static List<Integer> makeHierarchyFromPath(String path) {
// e.g. /10/3487/8345.png
// sure that replace() is correct? may be replaceAll() would be better?
path = path.replace(File.separatorChar, '/');
int p = path.indexOf('.');
if (p > 0) {
path = path.substring(0, p); // remove suffix
}
if (path.startsWith("/")) {
path = path.substring(1);
}
try {
List<Integer> hierarchy = new ArrayList<Integer>();
for (;;) {
p = path.indexOf('/');
if (p > 0) {
hierarchy.add(Integer.parseInt(path.substring(0, p)));
path = path.substring(p + 1);
} else {
hierarchy.add(Integer.parseInt(path));
break;
}
}
return hierarchy;
} catch (NumberFormatException e) {
return null;
}
}
public byte[] getData(String path) throws IOException {
return getData(makeHierarchyFromPath(path));
}
public byte[] getData(List<Integer> keyHierarchy) throws IOException {
ArrayList<Integer> posHierarchy = new ArrayList<Integer>();
posHierarchy.addAll(keyHierarchy);
posHierarchy.add(1); // position
long indexPosition = getValue(posHierarchy);
if (indexPosition == -1) {
return null;
}
ArrayList<Integer> sizeHierarchy = new ArrayList<Integer>();
sizeHierarchy.addAll(keyHierarchy);
sizeHierarchy.add(2); // size
int size = (int) getValue(sizeHierarchy);
if (size == -1) {
return null;
}
byte[] buf = new byte[size];
raf.seek(RunGPSAtlasHeader.HEADER_SIZE + rah.indexSize + indexPosition);
raf.readFully(buf);
return buf;
}
public void setValue(List<Integer> keyHierarchy, long value) throws IOException {
setValueImpl(getRootNode(), keyHierarchy, value);
}
public void setValue(String path, long value) throws IOException {
setValue(makeHierarchyFromPath(path), value);
}
private void setValueImpl(RAINode currentNode, List<Integer> keyHierarchy, long value) throws IOException {
int key = keyHierarchy.get(0);
keyHierarchy.remove(0);
RAINode n = currentNode.getChildNode(key, true);
if (keyHierarchy.isEmpty()) {
n.value = value;
n.write();
} else {
setValueImpl(n, keyHierarchy, value);
}
}
public long getValue(List<Integer> keyHierarchy) throws IOException {
RAINode n = getRootNode().getChildNode(keyHierarchy);
return n == null ? -1L : n.value;
}
public long getValue(String path) throws IOException {
return getValue(makeHierarchyFromPath(path));
}
public void setString(List<Integer> keyHierarchy, String value) throws IOException {
byte[] data = value.getBytes(Charsets.UTF_8);
addData(keyHierarchy, data);
}
public void setString(String path, String value) throws IOException {
setString(makeHierarchyFromPath(path), value);
}
public String getString(List<Integer> keyHierarchy) throws IOException {
byte[] data = getData(keyHierarchy);
if (data != null) {
return new String(data, Charsets.UTF_8);
}
return null;
}
public String getString(String path) throws IOException {
return getString(makeHierarchyFromPath(path));
}
}