/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.client;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import freenet.client.FetchException.FetchExceptionMode;
import freenet.client.InsertException.InsertExceptionMode;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
import freenet.support.io.StorageFormatException;
/**
* Essentially a map of integer to incrementible integer.
* FIXME maybe move this to support, give it a better name?
*
* WARNING: Changing non-transient members on classes that are Serializable can result in
* restarting downloads or losing uploads.
*/
public class FailureCodeTracker implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
public final boolean insert;
private int total;
public FailureCodeTracker(boolean insert) {
this.insert = insert;
}
/**
* Create a FailureCodeTracker from a SimpleFieldSet.
* @param isInsert Whether this is an insert.
* @param fs The SimpleFieldSet containing the FieldSet (non-verbose) form of
* the tracker.
*/
public FailureCodeTracker(boolean isInsert, SimpleFieldSet fs) {
this.insert = isInsert;
Iterator<String> i = fs.directSubsetNameIterator();
while(i.hasNext()) {
String name = i.next();
SimpleFieldSet f = fs.subset(name);
// We ignore the Description, if there is one; we just want the count
int num = Integer.parseInt(name);
int count = Integer.parseInt(f.get("Count"));
if(count < 0) throw new IllegalArgumentException("Count < 0");
map.put(Integer.valueOf(num), count);
total += count;
}
}
protected FailureCodeTracker() {
// For serialization.
this.insert = false;
}
private HashMap<Integer, Integer> map;
public void inc(FetchExceptionMode k) {
if(insert) throw new IllegalStateException();
inc(k.code);
}
public void inc(InsertExceptionMode k) {
if(!insert) throw new IllegalStateException();
inc(k.code);
}
public synchronized void inc(int k) {
if(k == 0) {
Logger.error(this, "Can't increment 0, not a valid failure mode", new Exception("error"));
}
if(map == null) map = new HashMap<Integer, Integer>();
Integer key = k;
Integer i = map.get(key);
if(i == null)
map.put(key, 1);
else
map.put(key, i+1);
total++;
}
public void inc(FetchExceptionMode k, int val) {
if(insert) throw new IllegalStateException();
inc(k.code, val);
}
public void inc(InsertExceptionMode k, int val) {
if(!insert) throw new IllegalStateException();
inc(k.code, val);
}
public synchronized void inc(Integer k, int val) {
if(k == 0) {
Logger.error(this, "Can't increment 0, not a valid failure mode", new Exception("error"));
}
if(map == null) map = new HashMap<Integer, Integer>();
Integer key = k;
Integer i = map.get(key);
if(i == null)
map.put(key, 1);
else
map.put(key, i+val);
total += val;
}
public synchronized String toVerboseString() {
if(map == null) return super.toString()+":empty";
StringBuilder sb = new StringBuilder();
for (Map.Entry<Integer, Integer> e : map.entrySet()) {
Integer x = e.getKey();
Integer val = e.getValue();
String s = getMessage(x);
sb.append(val);
sb.append('\t');
sb.append(s);
sb.append('\n');
}
return sb.toString();
}
public String getMessage(Integer x) {
return insert ? InsertException.getMessage(InsertExceptionMode.getByCode(x)) :
FetchException.getMessage(FetchExceptionMode.getByCode(x));
}
@Override
public synchronized String toString() {
if(map == null) return super.toString()+":empty";
StringBuilder sb = new StringBuilder(super.toString());
sb.append(':');
if(map.size() == 0) sb.append("empty");
else if(map.size() == 1) {
sb.append("one:");
Integer code = (Integer) (map.keySet().toArray())[0];
sb.append(code);
sb.append('=');
sb.append((map.get(code)));
} else if(map.size() < 10) {
boolean needComma = false;
for(Map.Entry<Integer, Integer> entry : map.entrySet()) {
if(needComma)
sb.append(',');
sb.append(entry.getKey()); // code
sb.append('=');
sb.append(entry.getValue());
needComma = true;
}
} else {
sb.append(map.size());
}
return sb.toString();
}
/**
* Merge codes from another tracker into this one.
*/
public synchronized FailureCodeTracker merge(FailureCodeTracker source) {
if(source.map == null) return this;
if(map == null) map = new HashMap<Integer, Integer>();
for (Map.Entry<Integer, Integer> e : source.map.entrySet()) {
Integer k = e.getKey();
Integer item = e.getValue();
inc(k, item);
}
return this;
}
public void merge(FetchException e) {
if(insert) throw new IllegalStateException("Merging a FetchException in an insert!");
if(e.errorCodes != null) {
merge(e.errorCodes);
}
// Increment mode anyway, so we get the splitfile error as well.
inc(e.mode.code);
}
public synchronized int totalCount() {
return total;
}
/** Copy verbosely to a SimpleFieldSet */
public synchronized SimpleFieldSet toFieldSet(boolean verbose) {
SimpleFieldSet sfs = new SimpleFieldSet(false);
if(map != null) {
for (Map.Entry<Integer, Integer> e : map.entrySet()) {
Integer k = e.getKey();
Integer item = e.getValue();
int code = k.intValue();
// prefix.num.Description=<code description>
// prefix.num.Count=<count>
if(verbose)
sfs.putSingle(Integer.toString(code)+".Description", getMessage(code));
sfs.put(Integer.toString(code)+".Count", item);
}
}
return sfs;
}
public synchronized boolean isOneCodeOnly() {
if(map == null) return true;
return map.size() == 1;
}
public FetchExceptionMode getFirstCodeFetch() {
if(insert) throw new IllegalStateException();
return FetchExceptionMode.getByCode(getFirstCode());
}
public InsertExceptionMode getFirstCodeInsert() {
if(!insert) throw new IllegalStateException();
return InsertExceptionMode.getByCode(getFirstCode());
}
public synchronized int getFirstCode() {
return ((Integer) map.keySet().toArray()[0]).intValue();
}
public synchronized boolean isFatal(boolean insert) {
if(map == null) return false;
for (Map.Entry<Integer, Integer> e : map.entrySet()) {
Integer code = e.getKey();
if(e.getValue() == 0) continue;
if(insert) {
if(InsertException.isFatal(InsertExceptionMode.getByCode(code))) return true;
} else {
if(FetchException.isFatal(FetchExceptionMode.getByCode(code))) return true;
}
}
return false;
}
public void merge(InsertException e) {
if(!insert) throw new IllegalArgumentException("This is not an insert yet merge("+e+") called!");
if(e.errorCodes != null)
merge(e.errorCodes);
inc(e.getMode());
}
public synchronized boolean isEmpty() {
return map == null || map.isEmpty();
}
/** Copy the FailureCodeTracker. We implement Cloneable to shut up findbugs, but Object.clone() won't
* work because it's a shallow copy, so we implement it with merge(). */
@Override
public FailureCodeTracker clone() {
FailureCodeTracker tracker = new FailureCodeTracker(insert);
tracker.merge(this);
return tracker;
}
public synchronized boolean isDataFound() {
if(!insert) throw new IllegalStateException();
for(Map.Entry<Integer, Integer> entry : map.entrySet()) {
if(entry.getValue() <= 0) continue;
if(FetchException.isDataFound(FetchExceptionMode.getByCode(entry.getKey()), null)) return true;
}
return false;
}
private int MAGIC = 0xb605aa08;
private int VERSION = 1;
/** Get the length of the fixed-size representation produced by writeFixedLengthTo(). */
public static int getFixedLength(boolean insert) {
int upperLimit =
insert ? InsertException.UPPER_LIMIT_ERROR_CODE : FetchException.UPPER_LIMIT_ERROR_CODE;
return 4 + 4 + 4 + 4 * upperLimit;
}
/** Write a fixed-size representation to a DataOutputStream. This is important for e.g.
* splitfiles, where we have a fixed part of the disk file to save it to. */
public synchronized void writeFixedLengthTo(DataOutputStream dos) throws IOException {
int upperLimit =
insert ? InsertException.UPPER_LIMIT_ERROR_CODE : FetchException.UPPER_LIMIT_ERROR_CODE;
dos.writeInt(MAGIC);
dos.writeInt(VERSION);
dos.writeInt(upperLimit);
for(int i=0;i<upperLimit;i++)
dos.writeInt(getErrorCount(i));
}
/** Get number of errors of count mode */
public synchronized int getErrorCount(int mode) {
if(map == null) return 0;
Integer item = map.get(mode);
return item == null ? 0 : item;
}
/** Get number of errors of count mode */
public synchronized int getErrorCount(InsertExceptionMode mode) {
if(!insert) throw new IllegalStateException();
return getErrorCount(mode.code);
}
/** Get number of errors of count mode */
public synchronized int getErrorCount(FetchExceptionMode mode) {
if(insert) throw new IllegalStateException();
return getErrorCount(mode.code);
}
public FailureCodeTracker(boolean insert, DataInputStream dis) throws IOException, StorageFormatException {
this.insert = insert;
if(dis.readInt() != MAGIC)
throw new StorageFormatException("Bad magic for FailureCodeTracker");
if(dis.readInt() != VERSION)
throw new StorageFormatException("Bad version for FailureCodeTracker");
int upperLimit =
insert ? InsertException.UPPER_LIMIT_ERROR_CODE : FetchException.UPPER_LIMIT_ERROR_CODE;
if(dis.readInt() != upperLimit)
throw new StorageFormatException("Bad upper limit for FailureCodeTracker");
for(int i=0;i<upperLimit;i++) {
int x = dis.readInt();
if(x < 0) throw new StorageFormatException("Negative error counts");
if(x == 0) continue;
if(map == null) map = new HashMap<Integer, Integer>();
total += x;
map.put(i, x);
}
}
}