fujaba/NetworkParser

View on GitHub
src/main/java/de/uniks/networkparser/bytes/qr/Version.java

Summary

Maintainability
C
1 day
Test Coverage
package de.uniks.networkparser.bytes.qr;

import de.uniks.networkparser.json.JsonArray;
import de.uniks.networkparser.json.JsonObject;

/**
 * See ISO 18004:2006 Annex D
 *
 * @author Sean Owen
 */
public final class Version {

    /**
     * See ISO 18004:2006 Annex D. Element i represents the raw version bits that
     * specify version i + 7
     */
    private static final int[] VERSION_DECODE_INFO = { 0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, 0x0C762, 0x0D847,
            0x0E60D, 0x0F928, 0x10B78, 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, 0x168C9, 0x177EC, 0x18EC4, 0x191E1,
            0x1AFAB, 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, 0x2542E,
            0x26A64, 0x27541, 0x28C69 };

    private static Version[] VERSIONS;

    private final int versionNumber;
    private final int[] alignmentPatternCenters;
    private final ECB ecBlocks1;
    private final ECB ecBlocks2;
    private final ECB ecBlocks3;
    private final ECB ecBlocks4;
    private final int totalCodewords;

    private Version(int versionNumber, int[] alignmentPatternCenters, String json) {
        this.versionNumber = versionNumber;
        this.alignmentPatternCenters = alignmentPatternCenters;

        JsonObject jsonObject = new JsonObject();
        jsonObject.withFlag(JsonObject.ALLOWDUPLICATE);
        jsonObject.withValue(json);
        this.ecBlocks1 = getECBS(jsonObject, 0);
        this.ecBlocks2 = getECBS(jsonObject, 1);
        this.ecBlocks3 = getECBS(jsonObject, 2);
        this.ecBlocks4 = getECBS(jsonObject, 3);

        int total = 0;
        ECB element = ecBlocks1;
        int codeword = element.getECCodewordsPerBlock();
        while (element != null) {
            total += element.getCount() * (element.getDataCodewords() + codeword);
            element = element.next();
        }
        this.totalCodewords = total;
    }

    ECB getECBS(JsonObject jsonObject, int index) {
        if (jsonObject == null || jsonObject.size() < 1) {
            return new ECB(-1, -1, -1);
        }
        int ecCodeBlocks = Integer.valueOf(jsonObject.getKeyByIndex(index));
        JsonArray array = (JsonArray) jsonObject.getValueByIndex(index);
        ECB prev = null;
        ECB first = null;
        for (int z = 0; z < array.size(); z += 2) {
            ECB element = new ECB(ecCodeBlocks, (Integer) array.get(z), (Integer) array.get(z + 1));
            if (prev != null) {
                prev.withNext(element);
            }
            if (first == null) {
                first = element;
            }
            prev = element;
        }
        return first;
    }

    public int getVersionNumber() {
        return versionNumber;
    }

    public int[] getAlignmentPatternCenters() {
        return alignmentPatternCenters;
    }

    public int getTotalCodewords() {
        return totalCodewords;
    }

    public int getDimensionForVersion() {
        return 17 + 4 * versionNumber;
    }

    public ECB getECBlocksForLevel(ErrorCorrectionLevel ecLevel) {
        if (ecLevel == null) {
            return null;
        }
        if (ecLevel.ordinal() == 0) {
            return ecBlocks1;
        }
        if (ecLevel.ordinal() == 1) {
            return ecBlocks2;
        }
        if (ecLevel.ordinal() == 2) {
            return ecBlocks3;
        }
        if (ecLevel.ordinal() == 3) {
            return ecBlocks4;
        }
        return null;
    }

    /**
     * <p>
     * Deduces version information purely from QR Code dimensions.
     * </p>
     *
     * @param dimension dimension in modules
     * @return Version for a QR Code of that dimension
     */
    public static Version getProvisionalVersionForDimension(int dimension) {
        if (dimension % 4 != 1) {
            return null;
        }
        return getVersionForNumber((dimension - 17) >> 2);
    }

    public static Version getVersionForNumber(int versionNumber) {
        if (versionNumber < 1 || versionNumber > 40) {
            return null;
        }
        if (VERSIONS == null) {
            /**
             * See ISO 18004:2006 6.5.1 Table 9
             * 
             * @return Version-Array with Build Polynom
             */
            VERSIONS = new Version[] { new Version(1, new int[] {}, "{7:[1,19], 10:[1,16], 13:[1,13], 17:[1,9]}"),
                    new Version(2, new int[] { 6, 18 }, "{10:[1,34], 16:[1,28], 22:[1,22], 28:[1,16]}"),
                    new Version(3, new int[] { 6, 22 }, "{15:[1,55], 26:[1,44], 18:[2,17], 22:[2,13]}"),
                    new Version(4, new int[] { 6, 26 }, "{20:[1,80], 18:[2,32], 26:[2,24], 16:[4,9]}"),
                    new Version(5, new int[] { 6, 30 }, "{26:[1,108], 24:[2,43], 18:[2,15,2,16], 22:[2,11,2,12]}"),
                    new Version(6, new int[] { 6, 34 }, "{18:[2,68], 16:[4,27], 24:[4,19], 28:[4,15]}"),
                    new Version(7, new int[] { 6, 22, 38 }, "{20:[2,78], 18:[4,31], 18:[2,14,4,15], 26:[4,13,1,14]}"),
                    new Version(8, new int[] { 6, 24, 42 },
                            "{24:[2, 97], 22:[2, 38, 2, 39],22:[4, 18, 2, 19],26:[4, 14, 2, 15]}"),
                    new Version(9, new int[] { 6, 26, 46 },
                            "{30:[2, 116],22:[3, 36,2, 37], 20:[4, 16, 4, 17], 24:[4, 12, 4, 13]}"),
                    new Version(10, new int[] { 6, 28, 50 },
                            "{18:[2, 68, 2, 69], 26:[4, 43, 1, 44],24: [6, 19, 2, 20], 28:[6, 15,2, 16]}"),
                    new Version(11, new int[] { 6, 30, 54 },
                            "{20:[4, 81], 30:[1, 50,4, 51], 28:[4, 22,4, 23], 24:[3, 12,8, 13]}"),
                    new Version(12, new int[] { 6, 32, 58 },
                            "{24:[2, 92,2, 93],22:[6, 36,2, 37], 26:[4, 20,6, 21], 28:[7, 14,4, 15]}"),
                    new Version(13, new int[] { 6, 34, 62 },
                            "{26:[4, 107], 22:[8, 37,1, 38], 24:[8, 20,4, 21], 22:[12, 11,4, 12]}"),
                    new Version(14, new int[] { 6, 26, 46, 66 },
                            "{30:[3, 115,1, 116], 24:[4, 40,5, 41], 20:[11, 16,5, 17], 24:[11, 12,5, 13]}"),
                    new Version(15, new int[] { 6, 26, 48, 70 },
                            "{22:[5, 87,1, 88], 24:[5, 41,5, 42], 30:[5, 24,7, 25], 24:[11, 12,7, 13]}"),
                    new Version(16, new int[] { 6, 26, 50, 74 },
                            "{24:[5, 98,1, 99], 28:[7, 45,3, 46], 24:[15, 19,2, 20], 30:[3, 15,13, 16]}"),
                    new Version(17, new int[] { 6, 30, 54, 78 },
                            "{28:[1, 107,5, 108], 28:[10, 46,1, 47], 28:[1, 22,15, 23], 28:[2, 14,17, 15]}"),
                    new Version(18, new int[] { 6, 30, 56, 82 },
                            "{30:[5, 120,1, 121], 26:[9, 43,4, 44], 28:[17, 22,1, 23], 28:[2, 14,19, 15]}"),
                    new Version(19, new int[] { 6, 30, 58, 86 },
                            "{28:[3, 113,4, 114], 26:[3, 44,11, 45], 26:[17, 21,4, 22], 26:[9, 13,16, 14]}"),
                    new Version(20, new int[] { 6, 34, 62, 90 },
                            "{28:[3, 107,5, 108], 26:[3, 41,13, 42], 30:[15, 24,5, 25], 28:[15, 15,10, 16]}"),
                    new Version(21, new int[] { 6, 28, 50, 72, 94 },
                            "{28:[4, 116,4, 117], 26:[17, 42], 28:[17, 22,6, 23], 30:[19, 16,6, 17]}"),
                    new Version(22, new int[] { 6, 26, 50, 74, 98 },
                            "{28:[2, 111,7, 112], 28:[17, 46], 30:[7, 24,16, 25], 24:[34, 13]}"),
                    new Version(23, new int[] { 6, 30, 54, 78, 102 },
                            "{30:[4, 121,5, 122], 28:[4, 47,14, 48], 30:[11, 24,14, 25], 30:[16, 15,14, 16]}"),
                    new Version(24, new int[] { 6, 28, 54, 80, 106 },
                            "{30:[6, 117,4, 118], 28:[6, 45,14, 46], 30:[11, 24,16, 25], 30:[30, 16,2, 17]}"),
                    new Version(25, new int[] { 6, 32, 58, 84, 110 },
                            "{26:[8, 106,4, 107], 28:[8, 47,13, 48], 30:[7, 24,22, 25], 30:[22, 15,13, 16]}"),
                    new Version(26, new int[] { 6, 30, 58, 86, 114 },
                            "{28:[10, 114,2, 115], 28:[19, 46,4, 47], 28:[28, 22,6, 23], 30:[33, 16,4, 17]}"),
                    new Version(27, new int[] { 6, 34, 62, 90, 118 },
                            "{30:[8, 122,4, 123], 28:[22, 45,3, 46], 30:[8, 23,26, 24], 30:[12, 15,28, 16]}"),
                    new Version(28, new int[] { 6, 26, 50, 74, 98, 122 },
                            "{30:[3, 117,10, 118], 28:[3, 45,23, 46], 30:[4, 24,31, 25], 30:[11, 15,31, 16]}"),
                    new Version(29, new int[] { 6, 30, 54, 78, 102, 126 },
                            "{30:[7, 116,7, 117], 28:[21, 45,7, 46], 30:[1, 23,37, 24], 30:[19, 15,26, 16]}"),
                    new Version(30, new int[] { 6, 26, 52, 78, 104, 130 },
                            "{30:[5, 115,10, 116], 28:[19, 47,10, 48], 30:[15, 24,25, 25], 30:[23, 15,25, 16]}"),
                    new Version(31, new int[] { 6, 30, 56, 82, 108, 134 },
                            "{30:[13, 115,3, 116], 28:[2, 46,29, 47], 30:[42, 24,1, 25], 30:[23, 15,28, 16]}"),
                    new Version(32, new int[] { 6, 34, 60, 86, 112, 138 },
                            "{30:[17, 115], 28:[10, 46,23, 47], 30:[10, 24,35, 25], 30:[19, 15,35, 16]}"),
                    new Version(33, new int[] { 6, 30, 58, 86, 114, 142 },
                            "{30:[17, 115,1, 116], 28:[14, 46,21, 47], 30:[29, 24,19, 25], 30:[11, 15,46, 16]}"),
                    new Version(34, new int[] { 6, 34, 62, 90, 118, 146 },
                            "{30:[13, 115,6, 116], 28:[14, 46,23, 47], 30:[44, 24,7, 25], 30:[59, 16,1, 17]}"),
                    new Version(35, new int[] { 6, 30, 54, 78, 102, 126, 150 },
                            "{30:[12, 121,7, 122], 28:[12, 47,26, 48], 30:[39, 24,14, 25], 30:[22, 15,41, 16]}"),
                    new Version(36, new int[] { 6, 24, 50, 76, 102, 128, 154 },
                            "{30:[6, 121,14, 122], 28:[6, 47,34, 48], 30:[46, 24,10, 25], 30:[2, 15,64, 16]}"),
                    new Version(37, new int[] { 6, 28, 54, 80, 106, 132, 158 },
                            "{30:[17, 122,4, 123], 28:[29, 46,14, 47], 30:[49, 24,10, 25], 30:[24, 15,46, 16]}"),
                    new Version(38, new int[] { 6, 32, 58, 84, 110, 136, 162 },
                            "{30:[4, 122,18, 123], 28:[13, 46,32, 47], 30:[48, 24,14, 25], 30:[42, 15,32, 16]}"),
                    new Version(39, new int[] { 6, 26, 54, 82, 110, 138, 166 },
                            "{30:[20, 117,4, 118], 28:[40, 47,7, 48], 30:[43, 24,22, 25], 30:[10, 15,67, 16]}"),
                    new Version(40, new int[] { 6, 30, 58, 86, 114, 142, 170 },
                            "{30:[19, 118,6, 119], 28:[18, 47,31, 48], 30:[34, 24,34, 25], 30:[20, 15,61, 16]}") };
        }
        return VERSIONS[versionNumber - 1];
    }

    static Version decodeVersionInformation(int versionBits) {
        int bestDifference = Integer.MAX_VALUE;
        int bestVersion = 0;
        for (int i = 0; i < VERSION_DECODE_INFO.length; i++) {
            int targetVersion = VERSION_DECODE_INFO[i];
            /* Do the version info bits match exactly? done. */
            if (targetVersion == versionBits) {
                return getVersionForNumber(i + 7);
            }
            /*
             * Otherwise see if this is the closest to a real version info bit string we
             * have seen so far
             */
            int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion);
            if (bitsDifference < bestDifference) {
                bestVersion = i + 7;
                bestDifference = bitsDifference;
            }
        }
        /*
         * We can tolerate up to 3 bits of error since no two version info codewords
         * will differ in less than 8 bits.
         */
        if (bestDifference <= 3) {
            return getVersionForNumber(bestVersion);
        }
        /* If we didn't find a close enough match, fail */
        return null;
    }

    /**
     * See ISO 18004:2006 Annex E
     */
    BitMatrix buildFunctionPattern() {
        if (alignmentPatternCenters == null) {
            return null;
        }
        int dimension = getDimensionForVersion();
        BitMatrix bitMatrix = new BitMatrix(dimension);

        /* Top left finder pattern + separator + format */
        bitMatrix.setRegion(0, 0, 9, 9);
        /* Top right finder pattern + separator + format */
        bitMatrix.setRegion(dimension - 8, 0, 8, 9);
        /* Bottom left finder pattern + separator + format */
        bitMatrix.setRegion(0, dimension - 8, 9, 8);

        /* Alignment patterns */
        int max = alignmentPatternCenters.length;
        for (int x = 0; x < max; x++) {
            int i = alignmentPatternCenters[x] - 2;
            for (int y = 0; y < max; y++) {
                if ((x == 0 && (y == 0 || y == max - 1)) || (x == max - 1 && y == 0)) {
                    /* No alignment patterns near the three finder paterns */
                    continue;
                }
                bitMatrix.setRegion(alignmentPatternCenters[y] - 2, i, 5, 5);
            }
        }

        /* Vertical timing pattern */
        bitMatrix.setRegion(6, 9, 1, dimension - 17);
        /* Horizontal timing pattern */
        bitMatrix.setRegion(9, 6, dimension - 17, 1);

        if (versionNumber > 6) {
            /* Version info, top right */
            bitMatrix.setRegion(dimension - 11, 0, 3, 6);
            /* Version info, bottom left */
            bitMatrix.setRegion(0, dimension - 11, 6, 3);
        }

        return bitMatrix;
    }

    public String toString() {
        return String.valueOf(versionNumber);
    }

    /**
     * <p>
     * Encapsualtes the parameters for one error-correction block in one symbol
     * version. This includes the number of data codewords, and the number of times
     * a block with these parameters is used consecutively in the QR code version's
     * format.
     * </p>
     */
    public static final class ECB {
        private final int count;
        private final int dataCodewords;
        private final int ecCodewordsPerBlock;
        private ECB next;

        ECB(int ecCodewordsPerBlock, int count, int dataCodewords) {
            this.ecCodewordsPerBlock = ecCodewordsPerBlock;
            this.count = count;
            this.dataCodewords = dataCodewords;
        }

        public int getECCodewordsPerBlock() {
            return ecCodewordsPerBlock;
        }

        public ECB next() {
            return next;
        }

        public int getCount() {
            return count;
        }

        public int getDataCodewords() {
            return dataCodewords;
        }

        public ECB withNext(ECB value) {
            this.next = value;
            return this;
        }

        public int getNumBlocks() {
            int total = 0;
            ECB element = this;
            while (element != null) {
                total += element.getCount();
                element = element.next();
            }
            return total;
        }

        public int getTotalECCodewords() {
            return ecCodewordsPerBlock * getNumBlocks();
        }
    }
}