mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-05-24 15:44:31 +00:00
336 lines
8.9 KiB
Java
336 lines
8.9 KiB
Java
/*
|
|
* Copyright (C) 2010-2016 JPEXS, All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 3.0 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library.
|
|
*/
|
|
package com.jpexs.helpers;
|
|
|
|
import com.jpexs.decompiler.flash.helpers.Freed;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.ObjectInputStream;
|
|
import java.io.ObjectOutputStream;
|
|
import java.io.RandomAccessFile;
|
|
import java.util.AbstractMap;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
/**
|
|
*
|
|
* @author JPEXS
|
|
* @param <K>
|
|
* @param <V>
|
|
*/
|
|
public class FileHashMap<K, V> extends AbstractMap<K, V> implements Freed {
|
|
|
|
private static final Logger logger = Logger.getLogger(FileHashMap.class.getName());
|
|
|
|
private final Map<K, Integer> lengths = new HashMap<>();
|
|
|
|
private final Map<K, Long> offsets = new HashMap<>();
|
|
|
|
private long fileLen = 0;
|
|
|
|
private final RandomAccessFile file;
|
|
|
|
private final File fileName;
|
|
|
|
private final Set<Gap> gaps = new TreeSet<>();
|
|
|
|
private int maxGapLen = 0;
|
|
|
|
private boolean deleted = false;
|
|
|
|
private static class Gap implements Comparable<Gap> {
|
|
|
|
public long offset;
|
|
|
|
public int length;
|
|
|
|
public Gap(long offset, int length) {
|
|
this.offset = offset;
|
|
this.length = length;
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(Gap o) {
|
|
return o.length - length;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = 7;
|
|
hash = 97 * hash + (int) (this.offset ^ (this.offset >>> 32));
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (obj == null) {
|
|
return false;
|
|
}
|
|
if (getClass() != obj.getClass()) {
|
|
return false;
|
|
}
|
|
final Gap other = (Gap) obj;
|
|
if (this.offset != other.offset) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public static class FileEntry<K, V> implements Map.Entry<K, V> {
|
|
|
|
private final FileHashMap<K, V> parent;
|
|
|
|
private final K key;
|
|
|
|
public FileEntry(FileHashMap<K, V> parent, K key) {
|
|
this.parent = parent;
|
|
this.key = key;
|
|
}
|
|
|
|
@Override
|
|
public K getKey() {
|
|
return key;
|
|
}
|
|
|
|
@Override
|
|
public V getValue() {
|
|
return parent.get(key);
|
|
}
|
|
|
|
@Override
|
|
public V setValue(V value) {
|
|
return parent.put(key, value);
|
|
}
|
|
}
|
|
|
|
public FileHashMap(File file) throws IOException {
|
|
this.file = new RandomAccessFile(file, "rw");
|
|
this.file.setLength(0);
|
|
this.fileName = file;
|
|
file.deleteOnExit();
|
|
}
|
|
|
|
@Override
|
|
public boolean containsKey(Object key) {
|
|
if (deleted) {
|
|
throw new NullPointerException();
|
|
}
|
|
return offsets.containsKey(key);
|
|
}
|
|
|
|
@Override
|
|
public Set<K> keySet() {
|
|
if (deleted) {
|
|
throw new NullPointerException();
|
|
}
|
|
return offsets.keySet();
|
|
}
|
|
|
|
@Override
|
|
public V get(Object key) {
|
|
if (deleted) {
|
|
throw new NullPointerException();
|
|
}
|
|
try {
|
|
if (!offsets.containsKey(key)) {
|
|
return null;
|
|
}
|
|
long ofs = offsets.get(key);
|
|
int len = lengths.get(key);
|
|
file.seek(ofs);
|
|
byte[] data = new byte[len];
|
|
file.readFully(data);
|
|
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
|
|
try {
|
|
@SuppressWarnings("unchecked")
|
|
V ret = (V) ois.readObject();
|
|
return ret;
|
|
} catch (ClassNotFoundException ex) {
|
|
logger.log(Level.SEVERE, null, ex);
|
|
return null;
|
|
}
|
|
} catch (IOException ex) {
|
|
logger.log(Level.SEVERE, null, ex);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public synchronized V put(K key, V value) {
|
|
if (deleted) {
|
|
throw new NullPointerException();
|
|
}
|
|
ObjectOutputStream oos = null;
|
|
try {
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
oos = new ObjectOutputStream(baos);
|
|
oos.writeObject(value);
|
|
oos.flush();
|
|
byte[] data = baos.toByteArray();
|
|
if (offsets.containsKey(key)) {
|
|
long origOffset = offsets.get(key);
|
|
int origLen = lengths.get(key);
|
|
if (data.length <= origLen) {
|
|
file.seek(origOffset);
|
|
file.write(data);
|
|
lengths.put(key, data.length);
|
|
if (data.length < origLen) {
|
|
Gap g = new Gap(origOffset + data.length, origLen - data.length);
|
|
if (g.length > maxGapLen) {
|
|
maxGapLen = g.length;
|
|
}
|
|
gaps.add(g);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}
|
|
if (data.length <= maxGapLen) {
|
|
|
|
for (Iterator<Gap> i = gaps.iterator(); i.hasNext();) {
|
|
Gap g = i.next();
|
|
if (g.length >= data.length) {
|
|
file.seek(g.offset);
|
|
file.write(data);
|
|
offsets.put(key, g.offset);
|
|
lengths.put(key, g.length);
|
|
if (g.length > data.length) {
|
|
g.offset = g.offset + data.length;
|
|
g.length = g.length - data.length;
|
|
} else {
|
|
i.remove();
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
file.seek(fileLen);
|
|
file.write(data);
|
|
offsets.put(key, fileLen);
|
|
lengths.put(key, data.length);
|
|
fileLen += data.length;
|
|
}
|
|
} catch (IOException ex) {
|
|
logger.log(Level.SEVERE, null, ex);
|
|
} finally {
|
|
try {
|
|
oos.close();
|
|
} catch (IOException ex) {
|
|
// ignore
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public V remove(Object objKey) {
|
|
if (deleted) {
|
|
throw new NullPointerException();
|
|
}
|
|
@SuppressWarnings("unchecked")
|
|
K key = (K) objKey;
|
|
if (!containsKey(key)) {
|
|
return null;
|
|
}
|
|
V val = get((K) key);
|
|
Gap g = new Gap(offsets.get(key), lengths.get(key));
|
|
offsets.remove(key);
|
|
lengths.remove(key);
|
|
if (g.offset + g.length == fileLen) {
|
|
fileLen -= g.length;
|
|
} else {
|
|
if (g.length > maxGapLen) {
|
|
maxGapLen = g.length;
|
|
}
|
|
gaps.add(g);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
@Override
|
|
public Set<Entry<K, V>> entrySet() {
|
|
if (deleted) {
|
|
throw new NullPointerException();
|
|
}
|
|
Set<Entry<K, V>> ret = new HashSet<>();
|
|
for (K key : keySet()) {
|
|
ret.add(new FileEntry<>(this, key));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
if (deleted) {
|
|
throw new NullPointerException();
|
|
}
|
|
offsets.clear();
|
|
lengths.clear();
|
|
fileLen = 0;
|
|
maxGapLen = 0;
|
|
try {
|
|
file.setLength(0);
|
|
} catch (IOException ex) {
|
|
logger.log(Level.SEVERE, null, ex);
|
|
}
|
|
}
|
|
|
|
public void delete() {
|
|
if (deleted) {
|
|
throw new NullPointerException();
|
|
}
|
|
try {
|
|
file.close();
|
|
} catch (IOException ex) {
|
|
logger.log(Level.SEVERE, null, ex);
|
|
}
|
|
fileName.delete();
|
|
deleted = true;
|
|
|
|
}
|
|
|
|
@Override
|
|
public boolean isFreeing() {
|
|
return !deleted;
|
|
}
|
|
|
|
@Override
|
|
public void free() {
|
|
if (!deleted) {
|
|
delete();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return offsets.isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public int size() {
|
|
return offsets.size();
|
|
}
|
|
}
|