org.krysalis.barcode4j.impl.fourstate.USPSIntelligentMailLogicImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of barcode4j-fop-ext-0.20.5-complete Show documentation
Show all versions of barcode4j-fop-ext-0.20.5-complete Show documentation
Barcode4J is a flexible generator for barcodes written in Java inclusive
extensions to support Apache FOP 0.20.5.
The newest version!
/*
* Copyright 2008 Jeremias Maerki.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id: USPSIntelligentMailLogicImpl.java,v 1.1 2008/05/13 13:00:43 jmaerki Exp $ */
package org.krysalis.barcode4j.impl.fourstate;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.MissingResourceException;
import java.util.StringTokenizer;
import org.krysalis.barcode4j.ChecksumMode;
import org.krysalis.barcode4j.ClassicBarcodeLogicHandler;
/**
* This class is a logic implementation for the USPS Intelligent Mail Barcode (aka 4-State Customer
* Barcode, USPS-B-3200 revision E, 2007-10-30).
*/
public class USPSIntelligentMailLogicImpl extends AbstractFourStateLogicImpl {
static final char[] TABLE5OF13 = new char[1287];
static final char[] TABLE2OF13 = new char[78];
static final BarToCharacterMapping[] TABLE_BAR_TO_CHARACTER = new BarToCharacterMapping[65];
static {
initializeNof13Table(TABLE5OF13, 5, TABLE5OF13.length);
initializeNof13Table(TABLE2OF13, 2, TABLE2OF13.length);
initializeBarToCharacterTable();
}
/**
* Main constructor.
*/
public USPSIntelligentMailLogicImpl() {
super(ChecksumMode.CP_AUTO); //Special check mechanism!
}
private static int reverseUnsignedShort(int input) {
int reverse = 0;
for (int i = 0; i < 16; i++) {
reverse <<= 1;
reverse |= input & 1;
input >>= 1;
}
return reverse;
}
private static void initializeNof13Table(char[] tableNof13, int n, int tableLength) {
//Count up to 2^13 - 1 and find all those value that have N bits on
int lutLowerIndex = 0;
int lutUpperIndex = tableLength - 1;
for (int count = 0; count < 8192; count++) {
int bitCount = 0;
for (int bitIndex = 0; bitIndex < 13; bitIndex++) {
if ((count & (1 << bitIndex)) != 0) {
bitCount++;
}
}
//if we don't have the right number of bits on, go on to the next value
if (bitCount != n) {
continue;
}
//if the reverse is less than count, we have already visited this pair before
int reverse = reverseUnsignedShort(count) >> 3;
if (reverse < count) {
continue;
}
//if count is symmetric, place it at the first free slot from the end of the
//list. Otherwise, place it at the first free slot from the beginning of the
//list AND place reverse at the next free slot from the beginning of the list.
if (count == reverse) {
tableNof13[lutUpperIndex] = (char)count;
lutUpperIndex--;
} else {
tableNof13[lutLowerIndex] = (char)count;
lutLowerIndex++;
tableNof13[lutLowerIndex] = (char)reverse;
lutLowerIndex++;
}
}
//make sure the lower and upper parts of the table meet properly
if (lutLowerIndex != lutUpperIndex + 1) {
throw new IllegalStateException("lookup table indices didn't meet properly!");
}
}
private static final String BAR_TO_CHARACTER_TABLE_FILENAME
= "usps-4bc-bar-to-character-table.csv";
private static void initializeBarToCharacterTable() {
InputStream in = USPSIntelligentMailLogicImpl.class.getResourceAsStream(
BAR_TO_CHARACTER_TABLE_FILENAME);
if (in == null) {
throw new MissingResourceException(
"Resource " + BAR_TO_CHARACTER_TABLE_FILENAME + " not found!", null, null);
}
try {
BufferedReader reader = new BufferedReader(new java.io.InputStreamReader(in));
int idx = 0;
String line;
while ((line = reader.readLine()) != null) {
TABLE_BAR_TO_CHARACTER[idx] = new BarToCharacterMapping(idx, line);
idx++;
}
} catch (IOException ioe) {
throw new RuntimeException("Could not initialize constant due to I/O error: "
+ ioe.getMessage());
} finally {
try {
in.close();
} catch (IOException ioe) {
//ignore
}
}
}
private static final class BarToCharacterMapping {
private int descChar;
private int descBitMap;
private int ascChar;
private int ascBitMap;
private BarToCharacterMapping(int index, String line) {
StringTokenizer tokenizer = new StringTokenizer(line, ";");
if (Integer.parseInt(tokenizer.nextToken()) != (index + 1)) {
throw new IllegalStateException("Encountered the wrong line number");
}
this.descChar = tokenizer.nextToken().charAt(0) - 'A';
this.descBitMap = 1 << Integer.parseInt(tokenizer.nextToken());
this.ascChar = tokenizer.nextToken().charAt(0) - 'A';
this.ascBitMap = 1 << Integer.parseInt(tokenizer.nextToken());
}
}
/** {@inheritDoc} */
public char calcChecksum(String msg) {
return 0; //Not used
}
static BigInteger convertToBinary(String msg) {
if (msg.length() > 31) {
throw new IllegalArgumentException("Message must not be longer than 31 digits");
}
String routingCode = msg.substring(20);
int routingLength = routingCode.length();
BigInteger routingBig;
switch (routingLength) {
case 0:
routingBig = BigInteger.ZERO;
break;
case 5:
routingBig = new BigInteger(routingCode);
routingBig = routingBig.add(BigInteger.ONE);
break;
case 9:
routingBig = new BigInteger(routingCode);
routingBig = routingBig.add(BigInteger.valueOf(100000L + 1L));
break;
case 11:
routingBig = new BigInteger(routingCode);
routingBig = routingBig.add(BigInteger.valueOf(1000000000L + 100000L + 1L));
break;
default:
throw new IllegalArgumentException(
"Invalid length for the routing code. Expected 0, 5, 9 or 11 but got "
+ routingLength);
}
BigInteger binary = routingBig; //Set the rightmost 37 bits
final BigInteger five = BigInteger.valueOf(5);
final BigInteger ten = BigInteger.valueOf(10);
String trackingCode = msg.substring(0, 20);
//First tracking code digit
binary = binary.multiply(ten);
binary = binary.add(new BigInteger(trackingCode.substring(0, 1)));
//Second tracking code digit
binary = binary.multiply(five);
binary = binary.add(new BigInteger(trackingCode.substring(1, 2)));
//Remaining tracking code digits
for (int i = 2; i < 20; i++) {
binary = binary.multiply(ten);
binary = binary.add(new BigInteger(trackingCode.substring(i, i + 1)));
}
return binary;
}
static byte[] to13ByteArray(BigInteger binary) {
byte[] result = new byte[13];
byte[] bin = binary.toByteArray();
System.arraycopy(bin, 0, result, 13 - bin.length, bin.length);
return result;
}
private static final int GENERATOR_POLYNOMIAL = 0x0F35;
private static final int ELEVEN_BITS = 0x07FF;
static int calcFCS(byte[] binary) {
int frameCheckSequence = ELEVEN_BITS;
int data;
int startBit;
for (int byteIndex = 0; byteIndex < 13; byteIndex++) {
if (byteIndex == 0) {
//For the most significant byte skipping the 2 most significant bits
startBit = 2;
data = binary[0] << 5;
} else {
//For the rest of the bytes
startBit = 0;
data = binary[byteIndex] << 3;
}
for (int bit = startBit; bit < 8; bit++) {
if (((frameCheckSequence ^ data) & 0x400) != 0) {
frameCheckSequence = (frameCheckSequence << 1) ^ GENERATOR_POLYNOMIAL;
} else {
frameCheckSequence = (frameCheckSequence << 1);
}
frameCheckSequence &= ELEVEN_BITS;
data <<= 1;
}
}
return frameCheckSequence;
}
static int[] convertToCodewords(BigInteger binary) {
int[] codewords = new int[10];
BigInteger[] quotRem;
quotRem = binary.divideAndRemainder(BigInteger.valueOf(636));
codewords[9] = quotRem[1].intValue();
binary = quotRem[0];
final BigInteger const1365 = BigInteger.valueOf(1365);
for (int i = 8; i >= 1; i--) {
quotRem = binary.divideAndRemainder(const1365);
codewords[i] = quotRem[1].intValue();
binary = quotRem[0];
}
codewords[0] = binary.intValue();
return codewords;
}
static int[] modifyCodewords(int[] codewords, int fcs) {
int[] modified = new int[10];
//Codeword J is doubled (orientation information)
modified[9] = codewords[9] * 2;
System.arraycopy(codewords, 1, modified, 1, 8);
//Codeword A: process FCS
if ((fcs & 0x0400) != 0) {
modified[0] = codewords[0] + 659;
} else {
modified[0] = codewords[0];
}
return modified;
}
static char[] convertToCharacters(int[] codewords, int fcs) {
int c = codewords.length;
char[] chars = new char[c];
for (int i = 0; i < c; i++) {
int codeword = codewords[i];
if (codeword < TABLE5OF13.length) {
chars[i] = TABLE5OF13[codeword];
} else {
chars[i] = TABLE2OF13[codeword - TABLE5OF13.length];
}
if ((fcs & (1 << i)) != 0) {
chars[i] = (char)(~chars[i] & 0x1FFF); //bitwise negation
}
}
return chars;
}
static String convertToBars(char[] chars) {
StringBuffer bars = new StringBuffer(65);
bars.setLength(65);
for (int i = 0; i < bars.length(); i++) {
BarToCharacterMapping mapping = TABLE_BAR_TO_CHARACTER[i];
int resultBits = 0;
char c;
c = chars[mapping.ascChar];
if ((c & mapping.ascBitMap) != 0) {
resultBits |= 1;
}
c = chars[mapping.descChar];
if ((c & mapping.descBitMap) != 0) {
resultBits |= 2;
}
bars.setCharAt(i, (char)(resultBits + '0'));
}
return bars.toString();
}
/** {@inheritDoc} */
protected String[] encodeHighLevel(String msg) {
BigInteger binary = convertToBinary(msg);
int fcs = calcFCS(to13ByteArray(binary));
int[] codewords = convertToCodewords(binary);
int[] modified = modifyCodewords(codewords, fcs);
char[] chars = convertToCharacters(modified, fcs);
String bars = convertToBars(chars);
return new String[] {bars};
}
/** {@inheritDoc} */
protected String normalizeMessage(String msg) {
StringBuffer sb = new StringBuffer(msg.length());
for (int i = 0, c = msg.length(); i < c; i++) {
char ch = msg.charAt(i);
if (Character.isDigit(ch)) {
sb.append(ch);
}
}
return sb.toString();
}
/** {@inheritDoc} */
public void generateBarcodeLogic(ClassicBarcodeLogicHandler logic, String msg) {
//slightly specialized version of this method for USPS 4BC:
//-> there's no direct correlation between the original msg chars and the effective chars
String normalizedMsg = normalizeMessage(msg);
String[] encodedMsg = encodeHighLevel(normalizedMsg);
//encodedMsg.length will always be 1
logic.startBarcode(msg, normalizedMsg);
// encode message
String codeword = encodedMsg[0];
for (int i = 0, count = codeword.length(); i < count; i++) {
int height = Integer.parseInt(codeword.substring(i, i + 1));
logic.addBar(true, height);
}
logic.endBarcode();
}
}