package net.contrapunctus.rngzip.io; import java.io.IOException; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; import java.util.PriorityQueue; import net.contrapunctus.rngzip.util.BitInputStream; import net.contrapunctus.rngzip.util.BitOutputStream; /** * This class represents choice points using a straightforward * fixed-length bit encoding. * * <p class='license'>This is free software; you may modify and/or * redistribute it under the terms of the GNU General Public License, * but it comes with <b>absolutely no warranty.</b> * * @author Christopher League * @see SimpleChoiceFactory */ public class SimpleChoiceCoder implements ChoiceCoder, Comparable<SimpleChoiceCoder> { private static final boolean STATS = false; private static PriorityQueue<SimpleChoiceCoder> collection; private HashMap<Integer,Integer> histogram; static { if(STATS) { collection = new PriorityQueue<SimpleChoiceCoder>(); } } protected int limit, bits; protected Object id; /** * Create a choice point. You probably want to construct objects * using the {@link SimpleChoiceFactory} instead. * * @param limit the number of choices at this choice point, which * must be <b>greater than 1.</b> * * @param id this object is just used to represent the choice point * for debugging purposes—it may be null. If non-null, only its * ‘toString’ method will be called. * * @throws AssertionError if ‘limit’ is not greater than 1. */ SimpleChoiceCoder(int limit, Object id) { assert limit > 1; this.limit = limit; this.id = id; this.bits = (int) Math.ceil(Math.log(limit) / Math.log(2)); if(STATS) { histogram = new HashMap<Integer,Integer>(); collection.add(this); } } /** * Ignore this, it is just used internally to collect statistics * about choice points. */ public int compareTo(SimpleChoiceCoder that) { return that.limit - this.limit; } private void tick(int choice) { assert STATS; Integer i = histogram.get(choice); if(i == null) i = 0; histogram.put(choice, i+1); } public void encode(int choice, BitOutputStream bo) throws IOException { if(bo == null) throw new IllegalArgumentException("bo cannot be null"); if(choice < 0 || choice >= limit) throw new IndexOutOfBoundsException ("Choice "+choice+" is out of bounds for choice point " +this); bo.writeBits(choice, bits); if(STATS) tick(choice); } public int decode(BitInputStream bi) throws IOException { if(bi == null) throw new IllegalArgumentException("bi cannot be null"); int choice = (int) bi.readBits(bits); if(choice < 0 || choice >= limit) throw new RNGZFormatException ("input stream produced invalid choice "+choice+" at "+this); if(STATS) tick(choice); return choice; } /** * Identifies this choice point using the ‘id’ object provided to * the constructor (if it was non-null). */ public String toString() { if(id == null) return super.toString(); else return id.toString(); } /** * Ignore this, it is optionally used for gathering statistics * about choice points, but the class must be recompiled to support * this. */ public static void dumpStats(PrintStream out) { if(!STATS) return; while(!collection.isEmpty()) { SimpleChoiceCoder cd = collection.remove(); out.printf("Choice %s : %d%n", cd, cd.limit); int n = 0; for(Map.Entry<Integer, Integer> e : cd.histogram.entrySet()) { if(e.getValue() > 0) { out.printf(" %3d chosen %5d times%n", e.getKey(), e.getValue()); } n += e.getValue(); } out.printf(" (visited %d times)%n", n); } } }