如何将Java 2D Shape对象序列化为XML?

Shape界面由
Java 2D(Arc2D,Area,CubicCurve2D,Ellipse2D,GeneralPath等)的对象实现.

一些具体对象被标记为Serializable,可以使用对象序列化存储和恢复,但像Area这样的其他对象不会实现接口并抛出错误.

但是由于我们经常被警告,这种天真的序列化在Java实现或版本中不一定稳定,所以我更喜欢使用某种形式的序列化.

这导致我们使用XMLEncoder和XMLDecoder从XML存储/恢复,但是它能够处理更少的Java 2D Shape对象.

两者的一些结果可以在下面看到.我们从6个形状开始,并尝试通过对象序列化和标准XML序列化来存储/恢复它们.

我们将如何通过XML正确存储所有Shape对象?

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.beans.*;
import java.io.*;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.TitledBorder;

public class Serialize2D {

    private JPanel ui;

    Serialize2D() {
        initUI();
    }

    public void initUI() {
        if (ui != null) {
            return;
        }
        ui = new JPanel(new GridLayout(0,1));

        int[] xpoints = {205,295,205,295};
        int[] ypoints = {5,25,45};
        Polygon polygon = new Polygon(xpoints,ypoints,xpoints.length);

        ArrayList<Shape> shapes = new ArrayList<Shape>();
        int w = 45;
        shapes.add(new Rectangle2D.Double(5,5,90,40));
        shapes.add(new Ellipse2D.Double(105,40));
        shapes.add(polygon);
        shapes.add(new GeneralPath(new Rectangle2D.Double(5,55,40)));
        shapes.add(new Path2D.Double(new Rectangle2D.Double(105,40)));
        shapes.add(new Area(new Rectangle2D.Double(205,40)));

        addTitledLabelToPanel(shapes,"Original Shapes");
        addTitledLabelToPanel(
                serializeToFromObject(shapes),"Serialize via Object");
        addTitledLabelToPanel(
                serializeToFromXML(shapes),"Serialize via XML");
    }

    public JComponent getUI() {
        return ui;
    }

    public ArrayList<Shape> serializeToFromObject(ArrayList<Shape> shapes) {
        ArrayList<Shape> shps = new ArrayList<Shape>();
        try {
            ObjectOutputStream oos = null;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            for (Shape shape : shapes) {
                try {
                    oos.writeObject(shape);
                } catch (Exception ex) {
                    System.err.println(ex.toString());
                }
            }
            oos.flush();
            oos.close();
            System.out.println("length Obj: " + baos.toByteArray().length);
            ByteArrayInputStream bais = new ByteArrayInputStream(
                    baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);

            Object o = null;
            try {
                o = ois.readObject();
            } catch (NotSerializableException ex) {
                System.err.println(ex.getMessage());
            } catch (ClassNotFoundException ex) {
                ex.printStackTrace();
            }
            while (o != null) {
                shps.add((Shape) o);
                try {
                    o = ois.readObject();
                } catch (NotSerializableException ex) {
                    System.err.println(ex.getMessage());
                } catch (ClassNotFoundException ex) {
                    ex.printStackTrace();
                }
            }
            return shps;
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return shps;
    }

    public ArrayList<Shape> serializeToFromXML(ArrayList<Shape> shapes) {
        ArrayList<Shape> shps = new ArrayList<Shape>();
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            XMLEncoder xmle = new XMLEncoder(baos);
            for (Shape shape : shapes) {
                xmle.writeObject(shape);
            }
            xmle.flush();
            xmle.close();

            System.out.println("length XML: " + baos.toByteArray().length);
            ByteArrayInputStream bais
                    = new ByteArrayInputStream(baos.toByteArray());
            XMLDecoder xmld = new XMLDecoder(bais);
            Shape shape = (Shape) xmld.readObject();
            while (shape != null) {
                shps.add(shape);
                try {
                    shape = (Shape) xmld.readObject();
                } catch (ArrayIndexOutOfBoundsException aioobe) {
                    // we've read last object
                    shape = null;
                }
            }
            xmld.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return shps;
    }

    private final static String getType(Object o) {
        String s = o.getClass().getName();
        String[] parts = s.split("\\.");
        s = parts[parts.length - 1].split("\\$")[0];
        return s;
    }

    public static void drawShapesToImage(
            ArrayList<Shape> shapes,BufferedImage image) {
        Graphics2D g = image.createGraphics();
        g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(Color.WHITE);
        g.fillRect(0,image.getWidth(),image.getHeight());
        for (Shape shape : shapes) {
            String s = getType(shape);
            g.setColor(Color.GREEN);
            g.fill(shape);
            g.setColor(Color.BLACK);
            g.draw(shape);
            Rectangle r = shape.getBounds();
            int x = r.x + 5;
            int y = r.y + 16;
            if (r.width * r.height != 0) {
                g.drawString(s,x,y);
            }
        }

        g.dispose();
    }

    private void addTitledLabelToPanel(ArrayList<Shape> shapes,String title) {
        int w = 300;
        int h = 100;
        BufferedImage bi = new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
        drawShapesToImage(shapes,bi);
        JLabel l = new JLabel(new ImageIcon(bi));
        l.setBorder(new TitledBorder(title));
        ui.add(l);
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                Serialize2D ss = new Serialize2D();
                JOptionPane.showMessageDialog(null,ss.getUI());
            }
        };
        SwingUtilities.invokeLater(r);
    }
}

解决方法

不幸的是,使用XMLEncoder / Decoder的Shape to XML的天真编码/解码通常会破坏Shape的所有重要信息!

所以要做到这一点,仍然使用上面提到的类,我们序列化和恢复正确构造的bean,它们表示从PathIterator获得的形状部分.这些豆是:

> PathBean,它存储形成Java-2D Shape形状的PathSegment对象的集合.
> PathSegment,其存储路径的特定部分的细节(段类型,绕组规则和协调).

SerializeShapes GUI

用于演示存储和恢复形状的GUI.

>单击椭圆(Ellipse2D),Rectangle(Rectangle2D)或Face(Area)按钮几次.
>退出GUI.形状将被序列化到磁盘.
>重新启动GUI.上次随机绘制的形状将从磁盘&重新出现在GUI中.

所选形状将以绿色填充,其他形状将以红色填充.

package serialize2d;

import java.awt.*;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Random;
import java.util.Vector;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;


/** A GUI to make it easy to add/remove shapes from a canvas. 
 It should persist the shapes between runs.  */
public class SerializeShapes {

    JPanel ui;
    JPanel shapePanel;
    Random rand;
    JPanel shapeCanvas;
    DefaultListModel<Shape> allShapesModel;
    ListSelectionModel shapeSelectionModel;
    RenderingHints renderingHints;

    SerializeShapes() {
        initUI();
    }

    public void initUI() {
        if (ui != null) {
            return;
        }
        renderingHints = new RenderingHints(RenderingHints.KEY_DITHERING,RenderingHints.VALUE_DITHER_ENABLE);
        renderingHints.put(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        renderingHints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        renderingHints.put(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        renderingHints.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
        renderingHints.put(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_NORMALIZE);
        ui = new JPanel(new BorderLayout(4,4));
        ui.setBorder(new EmptyBorder(4,4,4));

        JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER,4));
        ui.add(controls,BorderLayout.PAGE_START);
        shapeCanvas = new ShapeCanvas();
        ui.add(shapeCanvas);
        rand = new Random();

        allShapesModel = new DefaultListModel<Shape>();
        JList<Shape> allShapes = new JList<Shape>(allShapesModel);
        allShapes.setCellRenderer(new ShapeListCellRenderer());
        shapeSelectionModel = allShapes.getSelectionModel();
        shapeSelectionModel.setSelectionMode(
                ListSelectionModel.SINGLE_SELECTION);
        ListSelectionListener shapesSelectionListener
                = new ListSelectionListener() {

                    @Override
                    public void valueChanged(ListSelectionEvent e) {
                        shapeCanvas.repaint();
                    }
                };
        allShapes.addListSelectionListener(shapesSelectionListener);

        JScrollPane shapesScroll = new JScrollPane(
                allShapes,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
        );
        // TODO fix this hack..
        shapesScroll.getViewport().setPreferredSize(new Dimension(60,200));
        ui.add(shapesScroll,BorderLayout.LINE_START);

        Action addEllipse = new AbstractAction("Ellipse") {

            @Override
            public void actionPerformed(ActionEvent e) {
                int w = rand.nextInt(100) + 10;
                int h = rand.nextInt(100) + 10;
                int x = rand.nextInt(shapeCanvas.getWidth() - w);
                int y = rand.nextInt(shapeCanvas.getHeight() - h);
                Ellipse2D ellipse = new Ellipse2D.Double(x,y,w,h);
                addShape(ellipse);
            }
        };
        addEllipse.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_E);

        Action addRectangle = new AbstractAction("Rectangle") {

            @Override
            public void actionPerformed(ActionEvent e) {
                int w = rand.nextInt(100) + 10;
                int h = rand.nextInt(100) + 10;
                int x = rand.nextInt(shapeCanvas.getWidth() - w);
                int y = rand.nextInt(shapeCanvas.getHeight() - h);
                Rectangle2D rectangle = new Rectangle2D.Double(x,h);
                addShape(rectangle);
            }
        };
        addRectangle.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_R);

        final int faceStart = 128513;
        final int faceEnd = 128528;
        final int diff = faceEnd - faceStart;
        StringBuilder sb = new StringBuilder();
        for (int count = faceStart; count <= faceEnd; count++) {
            sb.append(Character.toChars(count));
        }
        final String s = sb.toString();
        Vector<Font> compatibleFontList = new Vector<Font>();
        GraphicsEnvironment ge
                = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Font[] fonts = ge.getAllFonts();
        for (Font font : fonts) {
            if (font.canDisplayUpTo(s) < 0) {
                compatibleFontList.add(font);
            }
        }
        JComboBox fontChooser = new JComboBox(compatibleFontList);
        ListCellRenderer fontRenderer = new DefaultListCellRenderer() {

            @Override
            public Component getListCellRendererComponent(
                    JList list,Object value,int index,boolean isSelected,boolean cellHasFocus) {
                Component c = super.getListCellRendererComponent(
                        list,value,index,isSelected,cellHasFocus);
                JLabel l = (JLabel) c;
                Font font = (Font) value;
                l.setText(font.getName());
                return l;
            }
        };
        fontChooser.setRenderer(fontRenderer);
        final ComboBoxModel<Font> fontModel = fontChooser.getModel();

        BufferedImage bi = new BufferedImage(1,1,BufferedImage.TYPE_INT_RGB);
        Graphics2D g = bi.createGraphics();
        final FontRenderContext fontRenderContext = g.getFontRenderContext();

        Action addFace = new AbstractAction("Face") {

            @Override
            public void actionPerformed(ActionEvent e) {
                int codepoint = faceStart + rand.nextInt(diff);
                String text = new String(Character.toChars(codepoint));

                Font font = (Font) fontModel.getSelectedItem();
                Area area = new Area(
                        font.deriveFont(80f).
                        createGlyphVector(fontRenderContext,text).
                        getOutline());
                Rectangle bounds = area.getBounds();
                float x = rand.nextInt(
                        shapeCanvas.getWidth() - bounds.width) - bounds.x;
                float y = rand.nextInt(
                        shapeCanvas.getHeight() - bounds.height) - bounds.y;
                AffineTransform move = AffineTransform.
                        getTranslateInstance(x,y);
                area.transform(move);
                addShape(area);
            }
        };
        addFace.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_F);

        Action delete = new AbstractAction("Delete") {

            @Override
            public void actionPerformed(ActionEvent e) {
                int idx = shapeSelectionModel.getMinSelectionIndex();
                if (idx < 0) {
                    JOptionPane.showMessageDialog(
                            ui,"Select a shape to delete","Select a Shape",JOptionPane.ERROR_MESSAGE);
                } else {
                    allShapesModel.removeElementAt(idx);
                    shapeCanvas.repaint();
                }
            }
        };
        delete.putValue(Action.MNEMONIC_KEY,KeyEvent.VK_D);

        controls.add(new JButton(addEllipse));
        controls.add(new JButton(addRectangle));
        controls.add(new JButton(addFace));
        controls.add(fontChooser);
        controls.add(new JButton(delete));

        try {
            ArrayList<Shape> shapes = deserializeShapes();
            for (Shape shape : shapes) {
                allShapesModel.addElement(shape);
            }
        } catch (Exception ex) {
            System.err.println("If first launch,this is as expected!");
            ex.printStackTrace();
        }
    }

    private void addShape(Shape shape) {
        allShapesModel.addElement(shape);
        int size = allShapesModel.getSize() - 1;
        shapeSelectionModel.addSelectionInterval(size,size);
    }

    class ShapeCanvas extends JPanel {

        ShapeCanvas() {
            setBackground(Color.WHITE);
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHints(renderingHints);
            Stroke stroke = new BasicStroke(1.5f);
            g2.setStroke(stroke);
            int idx = shapeSelectionModel.getMinSelectionIndex();
            Shape selectedShape = null;
            if (idx > -1) {
                selectedShape = allShapesModel.get(idx);
            }
            Enumeration en = allShapesModel.elements();
            while (en.hasMoreElements()) {
                Shape shape = (Shape) en.nextElement();
                if (shape.equals(selectedShape)) {
                    g2.setColor(new Color(0,255,191));
                } else {
                    g2.setColor(new Color(255,191));
                }
                g2.fill(shape);
                g2.setColor(new Color(0,224));
                g2.draw(shape);
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(500,300);
        }
    }

    public JComponent getUI() {
        return ui;
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                SerializeShapes se = new SerializeShapes();

                JFrame f = new JFrame("Serialize Shapes");
                f.addWindowListener(new SerializeWindowListener(se));
                f.setDefaultCloSEOperation(JFrame.DO_NOTHING_ON_CLOSE);
                f.setContentPane(se.getUI());
                f.setResizable(false);
                f.pack();
                f.setLocationByPlatform(true);
                f.setVisible(true);
            }
        };
        SwingUtilities.invokeLater(r);
    }

    public void serializeShapes() throws FileNotFoundException {
        ArrayList<Shape> shapes
                = new ArrayList<Shape>();
        Enumeration en = allShapesModel.elements();
        while (en.hasMoreElements()) {
            Shape shape = (Shape) en.nextElement();
            shapes.add(shape);
        }
        ShapeIO.serializeShapes(shapes,this.getClass());
        try {
            Desktop.getDesktop().open(
                    ShapeIO.getSerializeFile(this.getClass()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public ArrayList<Shape> deserializeShapes() throws FileNotFoundException {
        return ShapeIO.deserializeShapes(this.getClass());
    }

    class ShapeListCellRenderer extends DefaultListCellRenderer {

        @Override
        public Component getListCellRendererComponent(
                JList<? extends Object> list,boolean cellHasFocus) {
            Component c = super.getListCellRendererComponent(list,cellHasFocus);
            JLabel l = (JLabel) c;
            Shape shape = (Shape) value;
            ShapeIcon icon = new ShapeIcon(shape,40);
            l.setIcon(icon);
            l.setText("");

            return l;
        }
    }

    class ShapeIcon implements Icon {

        Shape shape;
        int size;

        ShapeIcon(Shape shape,int size) {
            this.shape = shape;
            this.size = size;
        }

        @Override
        public void paintIcon(Component c,Graphics g,int x,int y) {
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHints(renderingHints);
            Rectangle bounds = shape.getBounds();
            int xOff = -bounds.x;
            int yOff = -bounds.y;
            double xRatio = (double) bounds.width / (double) size;
            double yRatio = (double) bounds.height / (double) size;
            double ratio = xRatio > yRatio ? xRatio : yRatio;
            AffineTransform scale = AffineTransform.getScaleInstance(1 / ratio,1 / ratio);
            AffineTransform shift = AffineTransform.getTranslateInstance(xOff,yOff);

            AffineTransform totalTransform = new AffineTransform();

            totalTransform.concatenate(scale);
            totalTransform.concatenate(shift);

            Area b = new Area(shape).createTransformedArea(totalTransform);
            bounds = b.getBounds();

            g2.setColor(Color.BLACK);
            g2.fill(b);
        }

        @Override
        public int getIconWidth() {
            return size;
        }

        @Override
        public int getIconHeight() {
            return size;
        }
    }
}

class SerializeWindowListener extends WindowAdapter {

    SerializeShapes serializeShapes;

    SerializeWindowListener(SerializeShapes serializeShapes) {
        this.serializeShapes = serializeShapes;
    }

    @Override
    public void windowClosing(WindowEvent e) {
        try {
            serializeShapes.serializeShapes();
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
            System.exit(1);
        }
        System.exit(0);
    }
}

ShapeIO

dawei

【声明】:唐山站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。