Search code examples
javaserializationqr-codezxing

How can I serialize a HashMap with BitMatrix objects? (QR Codes / zxing)


I have a "static class" called QRGenerator, whose purpose is to generate BitMatrix objects and to generate BufferedImage objects from them. This is the code for it:

package ericsonwrp.republica.vintage.caixa;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.EnumMap;
import java.util.Map;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

public class QRGenerator {

    private static BufferedImage image = null;
    private static int size = 250;
    private static BitMatrix byteMatrix = null;

    public static BitMatrix generateBitMatrix(String codeText) {
        try {
            Map<EncodeHintType, Object> hintMap = new EnumMap<EncodeHintType, Object>(EncodeHintType.class);
            hintMap.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            hintMap.put(EncodeHintType.MARGIN, 1);
            hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
            QRCodeWriter qrCodeWriter = new QRCodeWriter();
            byteMatrix = qrCodeWriter.encode(codeText, BarcodeFormat.QR_CODE, size,
                    size, hintMap);
        } catch (WriterException e) {
            e.printStackTrace();
        }
        return byteMatrix;
    }

    public static BufferedImage generateImage(BitMatrix byteMatrix) {
        int width = byteMatrix.getWidth();
        image = new BufferedImage(width, width,
                BufferedImage.TYPE_INT_RGB);
        image.createGraphics();
        Graphics2D graphics = (Graphics2D) image.getGraphics();
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, width);
        graphics.setColor(Color.BLACK);
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < width; j++) {
                if (byteMatrix.get(i, j)) {
                    graphics.fillRect(i, j, 1, 1);
                }
            }
        }
        return image;
    }

}

I have another class called "RepublicaVintageFile", whose purpose is to define an unique file to store the "content" of my application, and to provide a method to serialize (And read, which is irrelevant for the question) this "content". This is the code for it:

package ericsonwrp.republica.vintage.caixa;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.HashMap;

public class RepublicaVintageFile {

    private HashMap<String, Object> content;

    public RepublicaVintageFile() {
        setContent(new HashMap<String, Object>());
    }

    public void serializeContent(String path) throws IOException {
        FileOutputStream fout = new FileOutputStream(path, true);
        ObjectOutput out = null;
        try {
          out = new ObjectOutputStream(fout);   
          out.writeObject(getContent());
        } finally {
          try {
            if (out != null) {
              out.close();
            }
          } catch (IOException ex) {
            // ignore close exception
          }
          try {
            fout.close();
          } catch (IOException ex) {
            // ignore close exception
          }
        }
    }

    public HashMap<String, Object> getContent() {
        return content;
    }

    public void setContent(HashMap<String, Object> content) {
        this.content = content;
    }

}

The "content" of my application include BitMatrix objects, which are saved under a label inside a HashMap in a different file (private HashMap<String, BitMatrix> qrItems;). Since serializing the final BufferedImage gives me a java.io.NotSerializableException, I've tried to serialize the BitMatrix objects. But, for my disappointment, that's also impossible. This is the stacktrace:

java.io.NotSerializableException: com.google.zxing.common.BitMatrix
    at java.io.ObjectOutputStream.writeObject0(Unknown Source)
    at java.io.ObjectOutputStream.writeObject(Unknown Source)
    at java.util.HashMap.internalWriteEntries(Unknown Source)
    at java.util.HashMap.writeObject(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
    at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
    at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
    at java.io.ObjectOutputStream.writeObject0(Unknown Source)
    at java.io.ObjectOutputStream.writeObject(Unknown Source)
    at java.util.HashMap.internalWriteEntries(Unknown Source)
    at java.util.HashMap.writeObject(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
    at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
    at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
    at java.io.ObjectOutputStream.writeObject0(Unknown Source)
    at java.io.ObjectOutputStream.writeObject(Unknown Source)
    at ericsonwrp.republica.vintage.caixa.RepublicaVintageFile.serializeContent(RepublicaVintageFile.java:26)
    at ericsonwrp.republica.vintage.caixa.MainFrame.openSaveAsDialog(MainFrame.java:204)
    at ericsonwrp.republica.vintage.caixa.MainFrame.access$1(MainFrame.java:177)
    at ericsonwrp.republica.vintage.caixa.MainFrame$4.actionPerformed(MainFrame.java:112)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
    at javax.swing.AbstractButton.doClick(Unknown Source)
    at javax.swing.plaf.basic.BasicMenuItemUI.doClick(Unknown Source)
    at javax.swing.plaf.basic.BasicMenuItemUI$Handler.mouseReleased(Unknown Source)
    at java.awt.Component.processMouseEvent(Unknown Source)
    at javax.swing.JComponent.processMouseEvent(Unknown Source)
    at java.awt.Component.processEvent(Unknown Source)
    at java.awt.Container.processEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Window.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$500(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)

Considering all this, I come to my question: How can I serialize a HashMap with BitMatrix objects? That's important, because I don't want to store 100 (Or more) separate .png files on a folder and have to read the images all the time in order to get their content.


Solution

  • I've managed to "save" and "load" the BitMatrix objects translating them into boolean 2-dimensional arrays.

    private HashMap<String, BitMatrix> qrMatrixes;
    private HashMap<String, boolean[][]> qrMatrixBooleanArrays;
    

    As you can see, I have a HashMap of < String, BitMatrix >, with an equivalent in boolean[][] format. That's how I make such "translation" (Transferring the bits from each BitMatrix objects to the boolean[][] arrays):

    for (Map.Entry<String, BitMatrix> e : getQrMatrixes().entrySet()) {
        boolean[][] b = new boolean[e.getValue().getHeight()][e.getValue().getWidth()];
        for (int j = 0; j < e.getValue().getHeight(); j++) {
            for (int k = 0; k < e.getValue().getWidth(); k++) {
                b[j][k] = e.getValue().get(j, k);
            }
        }
        qrMatrixBooleanArrays.put(e.getKey(), b);
    }
    

    Java's Serializer serializes such object (HashMap< String, boolean[][] >) with no problems, no matter how nested it is (I've imagined that it would work with primitive types, with an old-fashioned array). Here's the whole method to update the list with the QR Codes, after loading the file:

        public void updateList() {
            if (MainFrame.getCurrentFile() != null) {
                setQrMatrixes(new HashMap<String, BitMatrix>());
                setQrMatrixBooleanArrays(new HashMap<String, boolean[][]>());
                for (Map.Entry<String, boolean[][]> e : ((HashMap<String, boolean[][]>) MainFrame.getCurrentFile().getContent().get("Qr Codes")).entrySet()) {
                    getQrMatrixBooleanArrays().put(e.getKey(), e.getValue());
                }
                for (Map.Entry<String, boolean[][]> e : getQrMatrixBooleanArrays().entrySet()) {
                    BitMatrix b = new BitMatrix(e.getValue().length, e.getValue().length);
                    for (int i = 0; i < e.getValue().length; i++) {
                        for (int j = 0; j < e.getValue()[i].length; j++) {
                            if (e.getValue()[i][j] == true) {
                                b.set(i, j);
                            }
                        }
                    }
                    getQrMatrixes().put(e.getKey(), b);
                    qrListModel.addElement(e.getKey());
                }
            } else {
                System.out.println("No file is loaded.");
            }
        }
    

    I'm afraid it's a bit hard to be more clear. Explore the docs for the BitMatrix object, and just copy the contents into a primitive 2d array. Good luck!