Wednesday, October 22, 2008

UPDATED: Re-visiting MetaBuilder

I previously blogged about a new feature of MetaBuilder that I'm working on allows you to visit each object, as they are built, using a closure. I've updated the functionality so that the build visitor closure is given a CreateNodeEvent instance for each constructed node as follows:

public class CreateNodeEvent {
def name
def node
def parent
def isRoot // set to true, if the new node is a root node
}

If not a root node, the closure should return the node, or possibly a different node, when it is done.

Using a Build Visitor to Build a List of Root Nodes
An example closure that builds a list of all root nodes might be:

{ if(isRoot) { myList << it.node }; it.node }

In fact, I thought that usage might be so common that I included another set of buildList methods that returns a list of root nodes for you.

I'd also thought about exposing the ObjectGraphBuilder.postNodeCompletion() method, but it doesn't give you the node name, which is sometimes useful to have. It also doesn't tell you whether you're dealing with a root node (though you can probably derive that from the fact that parent == null for a root node.)

Using a Build Visitor to Build a Map of Root Nodes
Here's another build visitor example: say I have classes Order and OrderLine as follows and I want to use MetaBuilder to read Orders from a file into a map called orders, keyed on id:

class OrderLine {
def upc
def qty
def price
}

class Order {
def id
def lines = []
}

MetaBuilder mb = new MetaBuilder(getClass().getClassLoader())
mb.define {
order(factory: Order) {
properties {
id(req: true)
}
collections {
lines {
line (factory: OrderLine) {
properties {
upc(req: true)
qty(req: true)
price(req: true)
}
}
}
}
}
}

def orders = [:]
mb.build( { if(it.isRoot) { orders[it.node.id] = it.node }; it.node }, new File("orders.mb").toURL() )
A Hibernate Example
Andres Almiray's recent blog post on using ObjectGraphBuilder to create Grails fixtures inspired this next example, which extends the previous
example to insert each object into a database using Hibernate:

Session session = HibernateUtil.getSessionFactory().getCurrentSession();

session.beginTransaction()

mb.build( { if(it.isRoot) { session.save(it.node) }; it.node }, new File("orders.mb").toURL() )

session.getTransaction().commit()
Final Thoughts
One of the benefits of this feature is that it enables you to process large numbers of build objects without having to incur the overhead of building a list to contain them all.

Wednesday, October 15, 2008

Expanding barcode scans into EANs

*UPDATED*
I just discovered a bug in the check digit calculation, fixed below.  Essentially, the check digit is only subtracted from 10 if the sum_of_totals % 10 is greater than 0.  I'm sorry for any inconvenience!

I recently had a project where I had to use a barcode scanner to make scans of numbers that were possibly smaller than the 13 digit EAN standard. I had to then check these against the same number in a list, but the numbers in the list had been expanded to 13 digits using the algorithm below:

If the scanned number has 13 or more digits, return it.
If the scanned number has 12 digits, prepend a zero and return the result.

If the scanned number had 6 digits, treat it as a Zero Compressed UPC-E, expand it 11 digits using the following table and continue:

Last digit UPC-E equivalent is UPC-A equivalent is
0 XXNNN0 0XX000-00NNN + check
1 XXNNN1 0XX100-00NNN + check
2 XXNNN2 0XX200-00NNN + check
3 XXXNN3 0XXX00-000NN + check
4 XXXXN4 0XXXX0-0000N + check
5 XXXXX5 0XXXXX-00005 + check
6 XXXXX6 0XXXXX-00006 + check
7 XXXXX7 0XXXXX-00007 + check
8 XXXXX8 0XXXXX-00008 + check
9 XXXXX9 0XXXXX-00009 + check

If the number (as scanned or expanded) has less than 12 digits, left pad it to 12 digits using zeros, add a check digit, and return the result.

The check digit is computed as follows:
  1. Sum the digits in all odd positions, and multiply the result by 3.
  2. Sum the digits in all even positions.
  3. Sum the totals calculated in steps 2 and 3.
  4. *UPDATED* Check digit is sum_of_totals % 10 > 0 ? 10 - (sum_of_totals % 10) : 0
Here is my C# program that I used to work this out:
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1 {
    class Program {
        protected static int[][] upcPartialChecksumDigit = new int[][]{ 
            new int[] { 0 * 1, 1 * 1, 2 * 1, 3 * 1, 4 * 1, 5 * 1, 6 * 1, 7 * 1, 8 * 1, 9 * 1 }, // 0
            new int[] { 0 * 3, 1 * 3, 2 * 3, 3 * 3, 4 * 3, 5 * 3, 6 * 3, 7 * 3, 8 * 3, 9 * 3 }, // 1
            new int[] { 0 * 1, 1 * 1, 2 * 1, 3 * 1, 4 * 1, 5 * 1, 6 * 1, 7 * 1, 8 * 1, 9 * 1 }, // 2
            new int[] { 0 * 3, 1 * 3, 2 * 3, 3 * 3, 4 * 3, 5 * 3, 6 * 3, 7 * 3, 8 * 3, 9 * 3 }, // 3
            new int[] { 0 * 1, 1 * 1, 2 * 1, 3 * 1, 4 * 1, 5 * 1, 6 * 1, 7 * 1, 8 * 1, 9 * 1 }, // 4
            new int[] { 0 * 3, 1 * 3, 2 * 3, 3 * 3, 4 * 3, 5 * 3, 6 * 3, 7 * 3, 8 * 3, 9 * 3 }, // 5
            new int[] { 0 * 1, 1 * 1, 2 * 1, 3 * 1, 4 * 1, 5 * 1, 6 * 1, 7 * 1, 8 * 1, 9 * 1 }, // 6
            new int[] { 0 * 3, 1 * 3, 2 * 3, 3 * 3, 4 * 3, 5 * 3, 6 * 3, 7 * 3, 8 * 3, 9 * 3 }, // 7
            new int[] { 0 * 1, 1 * 1, 2 * 1, 3 * 1, 4 * 1, 5 * 1, 6 * 1, 7 * 1, 8 * 1, 9 * 1 }, // 8
            new int[] { 0 * 3, 1 * 3, 2 * 3, 3 * 3, 4 * 3, 5 * 3, 6 * 3, 7 * 3, 8 * 3, 9 * 3 }, // 9
            new int[] { 0 * 1, 1 * 1, 2 * 1, 3 * 1, 4 * 1, 5 * 1, 6 * 1, 7 * 1, 8 * 1, 9 * 1 }, // 10
            new int[] { 0 * 3, 1 * 3, 2 * 3, 3 * 3, 4 * 3, 5 * 3, 6 * 3, 7 * 3, 8 * 3, 9 * 3 }  // 11
        };

        /*
        Note 6 digit UPC-E codes are expanded to 12 digit UPC-A codesas follows:
        0  XXNNN0  00XX000-00NNN + check
        1  XXNNN1  00XX100-00NNN + check
        2  XXNNN2  00XX200-00NNN + check
        3  XXXNN3  00XXX00-000NN + check
        4  XXXXN4  00XXXX0-0000N + check
        5  XXXXX5  00XXXXX-00005 + check
        6  XXXXX6  00XXXXX-00006 + check
        7  XXXXX7  00XXXXX-00007 + check
        8  XXXXX8  00XXXXX-00008 + check
        9  XXXXX9  00XXXXX-00009 + check
        */
        public static string getUPC(string id) {
            if (id == null) return null;

            StringBuilder s = new StringBuilder("0", 11);
            if (id.Length == 6) {
                switch (id[5] - '0') {
                    case 0:
                        id = s.Append(id[0]).Append(id[1]).Append("00000").Append(id[2]).Append(id[3]).Append(id[4]).ToString();
                        break;
                    case 1:
                        id = s.Append(id[0]).Append(id[1]).Append("10000").Append(id[2]).Append(id[3]).Append(id[4]).ToString();
                        break;
                    case 2:
                        id = s.Append(id[0]).Append(id[1]).Append("20000").Append(id[2]).Append(id[3]).Append(id[4]).ToString();
                        break;
                    case 3:
                        id = s.Append(id[0]).Append(id[1]).Append(id[2]).Append("00000").Append(id[3]).Append(id[4]).ToString();
                        break;
                    case 4:
                        id = s.Append(id[0]).Append(id[1]).Append(id[2]).Append(id[3]).Append("00000").Append(id[4]).ToString();
                        break;
                    case 5:
                        id = s.Append(id[0]).Append(id[1]).Append(id[2]).Append(id[3]).Append(id[4]).Append("00005").ToString();
                        break;
                    case 6:
                        id = s.Append(id[0]).Append(id[1]).Append(id[2]).Append(id[3]).Append(id[4]).Append("00006").ToString();
                        break;
                    case 7:
                        id = s.Append(id[0]).Append(id[1]).Append(id[2]).Append(id[3]).Append(id[4]).Append("00007").ToString();
                        break;
                    case 8:
                        id = s.Append(id[0]).Append(id[1]).Append(id[2]).Append(id[3]).Append(id[4]).Append("00008").ToString();
                        break;
                    case 9:
                        id = s.Append(id[0]).Append(id[1]).Append(id[2]).Append(id[3]).Append(id[4]).Append("00009").ToString();
                        break;
                    default:
                        return null;
                }
            }
            if (id.Length < 12) {
                id = id.PadLeft(12, '0');
                int checksum = 0;
                for (int i = 0; i < 12; i++) { //should get unrolled by compiler hopefully
                    int c = id[i] - '0';
                    if (c < 0 || c > 9) return null;
                    int partial = upcPartialChecksumDigit[i][c];
                    checksum += partial;
                }
                // *UPDATED*
                char checksumDigit = '0';
                checksum %= 10;
                if(checksum > 0) checksumDigit += (char)(10 - checksum);
                id += checksumDigit;
            }
            else if (id.Length == 12) {
                id = '0' + id;
            }
            return id;
        }
        


        static void Main(string[] args) {
            string s = ConvertUPC("654321");
            Console.WriteLine();
        }

        public static String ConvertUPC(string arg) {
            return arg + " -> " + getUPC(arg);
        }
    }
}
Content © didge

About Me

didge is my professional nickname, it's short for digital dave
Powered By Blogger