/*
* Copyright (C) 2008 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* version 2 as published by the Free Software Foundation.
*
* 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.
*/
package test.util;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.BitReader;
import uk.me.parabola.imgfmt.app.net.NumberStyle;
import uk.me.parabola.imgfmt.app.net.Numbers;
import static uk.me.parabola.imgfmt.app.net.NumberStyle.*;
/**
* This is a test reader of the numbering streams. Since there are multiple ways of writing
* the same set of house numbers, the only reasonable way of testing the write process is to
* read the bit stream back and compare with the intended numbers.
*
* There is no attempt at efficiency given it is for testing, but it is believed to correctly
* read numbers from any map.
*
* This code is derived directly from the NetDisplay class in the display project, so see that
* to see the development of this file.
* The algorithm that is required to read the bit stream was partly derived by studying the
* the released GPL code of cGPSmapper by Stanislaw Kozicki.
*
* @author Steve Ratcliffe
*/
public class NumberReader
{
private final BitReader br
;
// For reading the start differences and end difference numbers.
private VarBitReader startReader
;
private VarBitReader endReader
;
private VarBitReader savedStartReader
;
private VarBitReader savedEndReader
;
private boolean doRestoreBitWidths
;
// base numbers
private int leftBase
;
private int rightBase
;
// numbering styles
private NumberStyle leftStyle = ODD
;
private NumberStyle rightStyle = EVEN
;
// start numbers
private int leftStart
;
private int rightStart
;
// end numbers
private int leftEnd
;
private int rightEnd
;
// saved end numbers
private int leftLastEndDiff
;
private int rightLastEndDiff
;
// Numbers are a range between nodes. Keep count of them here
private int nodeCounter
;
private int numberOfNodes
;
public NumberReader
(BitReader br
) {
this.
br = br
;
}
public void setNumberOfNodes
(int numberOfNodes
) {
this.
numberOfNodes = numberOfNodes
;
}
/**
* Read the numbers into a list of Numbers classes.
* @param swap If the default starting position of left=ODD right=EVEN should be swapped.
* @return A list of the numbers that the input stream represents.
*/
public List<Numbers
> readNumbers
(boolean swap
) {
if (swap
) {
leftStyle = EVEN
;
rightStyle = ODD
;
}
getBitWidths
();
getInitialBase
();
List<Numbers
> numbers =
new ArrayList<>();
// To do this properly we need to know the number of nodes I think, this is the
// best we can do: if there are more than 8 bits left, there must be another command
// left. We could leave a short command at the end.
while (nodeCounter
< numberOfNodes +
1) {
try {
runCommand
(numbers
);
} catch (NumberException |
ArrayIndexOutOfBoundsException e
) {
//System.out.printf("collected %d, wanted %d\n", numbers.size(), numberOfNodes+1);
return numbers
;
}
}
return numbers
;
}
/**
* Get the bit widths for the start and end differences.
* Based on code for reading the RGN streams, but the signed bit is the
* opposite value.
* x is for start value differences. y is for end value differences.
*/
private void getBitWidths
() {
startReader =
new VarBitReader
(br,
5);
endReader =
new VarBitReader
(br,
2);
}
/**
* Decode the next command in the stream and run it.
* @param numbers When numbers are read, they are saved here.
*/
private void runCommand
(List<Numbers
> numbers
) throws NumberException
{
int cmd = readCommand
(); // fetch 1, 3 skip, 2 reload, 0 style
switch (cmd
) {
case 0:
changeStyles
();
break;
case 1:
fetchNumbers
(numbers
);
break;
case 2:
useBits
();
break;
case 6:
skipNodes
();
break;
default:
fail
("unimplemented command: " + cmd
);
}
}
/**
* Temporarily use a different bit width for the following number fetch.
*/
private void useBits
() {
if (!doRestoreBitWidths
) {
savedStartReader = startReader
;
savedEndReader = endReader
;
}
doRestoreBitWidths =
true;
if (br.
get1()) {
endReader =
new VarBitReader
(br,
2);
} else {
startReader =
new VarBitReader
(br,
5);
}
}
/**
* Skip nodes. For parts of a road that has no numbers.
*/
private void skipNodes
() {
boolean f = br.
get1();
int skip
;
if (f
)
skip =
1 + br.
get(10);
else
skip =
1 + br.
get(5);
nodeCounter += skip
;
}
/**
* Read the next command from the stream. Commands are variable length in the bit
* stream.
* 0 - numbering style (none, odd, even, both)
* 1 - fetch numbers
* 2 - change bit widths
* 6 - skip nodes
* @return The command number
*/
private int readCommand
() {
int cmd =
0;
if (br.
get1()) {
cmd |= 0x1
;
} else {
if (br.
get1()) {
cmd |= 0x2
;
if (br.
get1()) {
cmd |= 0x4
;
}
}
}
return cmd
;
}
/**
* Read the house numbers for a stretch of road.
*
* The start and end positions of the the left hand side of the road is first, followed
* by the right hand side of the road.
*
* The differences to the last point are stored. It is also possible to
* @param numbers When numbers are read, they are saved here.
*/
private void fetchNumbers
(List<Numbers
> numbers
) {
// If one side has no numbers, then there is only one set of numbers to calculate, but
// changes to base are applied to both sides.
boolean doSingleSide =
(leftStyle == NONE || rightStyle == NONE
);
if (leftStyle == NONE
)
leftBase = rightBase
;
// Check for command to copy the base number
boolean doSameBase =
false;
if (!doSingleSide
) {
doSameBase = br.
get1();
if (doSameBase
)
copyBase
();
}
//int abc = br.get(3);
boolean doRightOverride =
false;
if (!doSingleSide
)
doRightOverride =
!br.
get1();
boolean doReadStart =
!br.
get1();
boolean doReadEnd =
!br.
get1();
//item.addText("cmd: fetch numbers abc: %x", abc);
int startDiff =
0, endDiff = leftLastEndDiff
;
if (doReadStart
) {
startDiff = startReader.
read();
}
if (doReadEnd
) {
endDiff = endReader.
read();
}
leftStart = leftBase + startDiff
;
leftEnd = leftStart + endDiff
;
leftBase = leftEnd
;
leftLastEndDiff = endDiff
;
if (doSingleSide
) {
readSingleSide
(numbers
);
restoreReaders
();
return;
}
// *** Now for the right hand side numbers ***
// Note that endDiff falls through to this part
// start diff falls through at least when doSameBase is in force
if (!doSameBase
)
startDiff =
0;
// If we didn't read an endDiff value for the left side or right is different then
// default to the saved value.
if (doRightOverride ||
!doReadEnd
)
endDiff = rightLastEndDiff
;
doReadStart =
false;
doReadEnd =
false;
if (!doSameBase
)
doReadStart =
!br.
get1();
if (doRightOverride
)
doReadEnd =
!br.
get1();
if (doReadStart
)
startDiff = startReader.
read();
if (doReadEnd
)
endDiff = endReader.
read();
rightStart = rightBase + startDiff
;
rightEnd = rightStart + endDiff
;
rightBase = rightEnd
;
rightLastEndDiff = endDiff
;
adjustValues
();
Numbers n =
new Numbers
();
n.
setIndex(nodeCounter
);
n.
setNumbers(Numbers.
LEFT, leftStyle, leftStart, leftEnd
);
n.
setNumbers(Numbers.
RIGHT, rightStyle, rightStart, rightEnd
);
numbers.
add(n
);
nodeCounter++
;
restoreReaders
();
}
/**
* After a temporary bit width change.
*/
private void restoreReaders
() {
if (doRestoreBitWidths
) {
startReader = savedStartReader
;
endReader = savedEndReader
;
doRestoreBitWidths =
false;
}
}
/**
* If the road has numbers on just one side, then there is a shortened reading routine.
* The left variables are mostly used during reading regardless of which side of the
* road has numbers. Make everything work here.
* @param numbers The output list that the number record should be added to.
*/
private void readSingleSide
(List<Numbers
> numbers
) {
rightBase = leftBase
;
rightStart = leftStart
;
rightEnd = leftEnd
;
rightLastEndDiff = leftLastEndDiff
;
adjustValues
();
Numbers n =
new Numbers
();
n.
setIndex(nodeCounter
);
if (leftStyle == NONE
)
n.
setNumbers(Numbers.
RIGHT, rightStyle, rightStart, rightEnd
);
else
n.
setNumbers(Numbers.
LEFT, leftStyle, leftStart, leftEnd
);
numbers.
add(n
);
nodeCounter++
;
}
/**
* When it is known if the numbers are odd or even, then a shorter bitstream is made
* by taking advantage of that fact. This leaves the start and end points needing
* adjustment to made them odd or even as appropriate.
*/
private void adjustValues
() {
int ldirection =
1; // direction start is adjusted in; end in the opposite direction.
if (leftStart
< leftEnd
)
leftEnd--
;
else if (leftStart
> leftEnd
) {
leftEnd++
;
ldirection = -
1;
}
int rdirection =
1; // direction start is adjusted in; end in the opposite direction.
if (rightStart
< rightEnd
)
rightEnd--
;
else if (rightStart
> rightEnd
) {
rightEnd++
;
rdirection = -
1;
}
if (leftStyle == EVEN
) {
if ((leftStart
& 1) ==
1) leftStart += ldirection
;
if ((leftEnd
& 1) ==
1) leftEnd -= ldirection
;
} else if (leftStyle == ODD
) {
if ((leftStart
& 1) ==
0) leftStart+=ldirection
;
if ((leftEnd
& 1) ==
0) leftEnd-=ldirection
;
}
if (rightStyle == EVEN
) {
if ((rightStart
& 1) ==
1) rightStart+=rdirection
;
if ((rightEnd
& 1) ==
1) rightEnd-=rdirection
;
} else if (rightStyle == ODD
) {
if ((rightStart
& 1) ==
0) rightStart+=rdirection
;
if ((rightEnd
& 1) ==
0) rightEnd-=rdirection
;
}
}
/**
* Copy one of the bases to the other so they have the same value.
* The source is determined by reading a bit from the input.
*/
private void copyBase
() {
boolean f2 = br.
get1();
if (f2
) {
rightBase = leftBase
;
} else {
leftBase = rightBase
;
}
}
/**
* Change the numbering styles for this section of roads.
*/
private void changeStyles
() {
leftStyle = fromInt
(br.
get(2));
rightStyle = fromInt
(br.
get(2));
}
/**
* Get the initial base value. The first number for this section of road (although a diff
* can be applied to it).
*
* @throws NumberException
*/
private void getInitialBase
() {
int extra =
0;
boolean b1 = br.
get1();
if (!b1
)
extra = br.
get(4);
leftBase = br.
get(5 + extra
);
rightBase = leftBase
;
}
/**
* For cases that are not implemented yet.
*/
private void fail
(String s
) throws NumberException
{
System.
out.
printf("ABANDON: %s\n", s
);
remainingBits
();
throw new NumberException
();
}
/**
* Just print out any remaining bits.
*
* Was mostly used during development, before the whole stream was decoded.
*/
private void remainingBits
() {
StringBuilder sb =
new StringBuilder();
while (br.
getBitPosition() < br.
getNumberOfBits()) {
sb.
insert(0, br.
get1() ? "1" :
"0");
}
System.
out.
print(sb.
toString());
}
}
/**
* Reads integers with specified numbers of bits and optionally with sign bits.
*/
class VarBitReader
{
private final boolean signed
; // read as signed values
private final boolean negative
; // all values are read as positive and then negated
private final int width
; // the number of bits
private final int off
; // a value to be added to width to get the true number to read.
private final BitReader br
;
public VarBitReader
(BitReader br,
int off
) {
this.
br = br
;
this.
off = off
;
negative = br.
get1();
signed = br.
get1();
width = br.
get(4);
}
public int read
() {
int val
;
if (signed
) {
val = br.
sget(width + off +
1);
} else {
val = br.
get(width + off
);
}
if (negative
)
val = -val
;
return val
;
}
public String toString
() {
return String.
format("sign=%b neg=%b width=%d+%d", signed, negative, width, off
);
}
}
class NumberException
extends RuntimeException {
}