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, 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, 5, 90, 40));
shapes.add(polygon);
shapes.add(new GeneralPath(new Rectangle2D.Double(5, 55, 90, 40)));
shapes.add(new Path2D.Double(new Rectangle2D.Double(105, 55, 90, 40)));
shapes.add(new Area(new Rectangle2D.Double(205, 55, 90, 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, 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);
}
}
答案 0 :(得分:10)
不幸的是,使用Shape
/ XMLEncoder
对Decoder
到XML的天真编码/解码通常会破坏Shape
的所有重要信息!
为了做到这一点,仍然使用上面提到的类,我们序列化并恢复正确构造的bean,它们表示从 PathIterator
获得的形状部分。这些bean是:
PathBean
,它存储形成Java-2D PathSegment
形状的Shape
个对象的集合。 PathSegment
存储路径特定部分的细节(段类型,缠绕规则和协调)。 SerializeShapes
GUI 用于演示存储和恢复形状的GUI。
Ellipse2D
), Rectangle (Rectangle2D
)或 Face ({{1} })按钮几次。
所选形状将以绿色填充,其他形状将以红色填充。
Area
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, 4));
JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER, 4, 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, y, w, 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, 0, 191));
} else {
g2.setColor(new Color(255, 0, 0, 191));
}
g2.fill(shape);
g2.setColor(new Color(0, 0, 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, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
Component c = super.getListCellRendererComponent(list, value, index,
isSelected, 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);
}
}
执行XML的I / O.
ShapeIO
package serialize2d;
import java.awt.Shape;
import java.beans.*;
import java.io.*;
import java.util.ArrayList;
public class ShapeIO {
/** Save the list of shapes to the file system. */
public static void serializeShapes(
ArrayList<Shape> shapes, Class serializeClass)
throws FileNotFoundException {
File f = getSerializeFile(serializeClass);
XMLEncoder xmle = new XMLEncoder(new FileOutputStream(f));
ArrayList<PathBean> pathSegmentsCollection = new ArrayList<>();
for (Shape shape : shapes) {
ArrayList<PathSegment> pathSegments =
BeanConverter.getSegmentsFromShape(shape);
PathBean as = new PathBean(pathSegments);
pathSegmentsCollection.add(as);
}
xmle.writeObject(pathSegmentsCollection);
xmle.flush();
xmle.close();
}
/** Load the list of shapes from the file system. */
public static ArrayList<Shape> deserializeShapes(Class serializeClass)
throws FileNotFoundException {
File f = getSerializeFile(serializeClass);
XMLDecoder xmld = new XMLDecoder(new FileInputStream(f));
ArrayList<PathBean> pathSegmentsCollection
= (ArrayList<PathBean>) xmld.readObject();
ArrayList<Shape> shapes = new ArrayList<Shape>();
for (PathBean pathSegments : pathSegmentsCollection) {
shapes.add(BeanConverter.getShapeFromSegments(pathSegments));
}
return shapes;
}
/** Provide an unique, reproducible & readable/writable path for a class. */
public static File getSerializeFile(Class serializeClass) {
File f = new File(System.getProperty("user.home"));
String[] nameParts = serializeClass.getCanonicalName().split("\\.");
f = new File(f, "java");
for (String namePart : nameParts) {
f = new File(f, namePart);
}
f.mkdirs();
f = new File(f, nameParts[nameParts.length-1] + ".xml");
return f;
}
}
从BeanConverter
获取PathIterator
并将其转换为可序列化的bean。将bean转换回Shape
。
GeneralPath
package serialize2d;
import java.awt.Shape;
import java.awt.geom.*;
import java.util.ArrayList;
/** Utility class to convert bean to/from a Shape. */
public class BeanConverter {
/** Convert a shape to a serializable bean. */
public static ArrayList<PathSegment> getSegmentsFromShape(Shape shape) {
ArrayList<PathSegment> shapeSegments = new ArrayList<PathSegment>();
for (
PathIterator pi = shape.getPathIterator(null);
!pi.isDone();
pi.next()) {
double[] coords = new double[6];
int pathSegmentType = pi.currentSegment(coords);
int windingRule = pi.getWindingRule();
PathSegment as = new PathSegment(
pathSegmentType, windingRule, coords);
shapeSegments.add(as);
}
return shapeSegments;
}
/** Convert a serializable bean to a shape. */
public static Shape getShapeFromSegments(PathBean shapeSegments) {
GeneralPath gp = new GeneralPath();
for (PathSegment shapeSegment : shapeSegments.getPathSegments()) {
double[] coords = shapeSegment.getCoords();
int pathSegmentType = shapeSegment.getPathSegmentType();
int windingRule = shapeSegment.getWindingRule();
gp.setWindingRule(windingRule);
if (pathSegmentType == PathIterator.SEG_MOVETO) {
gp.moveTo(coords[0], coords[1]);
} else if (pathSegmentType == PathIterator.SEG_LINETO) {
gp.lineTo(coords[0], coords[1]);
} else if (pathSegmentType == PathIterator.SEG_QUADTO) {
gp.quadTo(coords[0], coords[1], coords[2], coords[3]);
} else if (pathSegmentType == PathIterator.SEG_CUBICTO) {
gp.curveTo(
coords[0], coords[1], coords[2],
coords[3], coords[4], coords[5]);
} else if (pathSegmentType == PathIterator.SEG_CLOSE) {
gp.closePath();
} else {
System.err.println("Unexpected value! " + pathSegmentType);
}
}
return gp;
}
}
在可串行化的bean中存储路径段的集合。
PathBean
package serialize2d;
import java.awt.geom.*;
import java.util.ArrayList;
/** PathBean stores the collection of PathSegment objects
that constitute the path of a Shape. */
public class PathBean {
public ArrayList<PathSegment> pathSegments;
public PathBean() {}
public PathBean(ArrayList<PathSegment> pathSegments) {
this.pathSegments = pathSegments;
}
public ArrayList<PathSegment> getPathSegments() {
return pathSegments;
}
public void setPathSegments(ArrayList<PathSegment> pathSegments) {
this.pathSegments = pathSegments;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
for (PathSegment pathSegment : pathSegments) {
sb.append(" \n\t");
sb.append(pathSegment.toString());
}
sb.append(" \n");
sb.append("}");
return "PathSegments: " + sb.toString();
}
}
存储整个路径的一部分的路径段。
PathSegment
这旨在作为概念证明,而不是抛光方法。
可以从我的云端硬盘下载zip archive of the source files in this answer。
答案 1 :(得分:1)
自定义PersistenceDelegate
可与XMLEncoder
一起使用,以将Path2D
或GeneralPath
序列化为XML。
考虑以下XML:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_60" class="java.beans.XMLDecoder">
<object class="java.awt.geom.Path2D$Float">
<void property="windingRule">
<int>0</int>
</void>
<void method="moveTo">
<float>1.0</float>
<float>1.0</float>
</void>
<void method="lineTo">
<float>2.0</float>
<float>0.0</float>
</void>
<void method="lineTo">
<float>0.0</float>
<float>3.0</float>
</void>
<void method="closePath"/>
</object>
</java>
当XMLEncoder
实例读取时,将执行以下命令...
Path2D.Float object = new Path2D.Float();
object.setWindingRule(0); // Note: 0 => Path2D.WIND_EVEN_ODD
object.moveTo(1.0, 1.0);
object.lineTo(2.0, 0.0);
object.lineTo(0.0, 3.0);
object.closePath();
... XMLDecoder.readObject()
将返回一个封闭的三角形对象。
基于此,我们可以得出结论:XMLDecoder
已经可以对Path2D
形状进行反序列化(如果它已正确编码)。 XMLEncoder
现在为我们做了什么?
Path2D.Float path = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 10);
path.moveTo(1, 1);
path.lineTo(2, 0);
path.lineTo(0, 3);
path.closePath();
try (XMLEncoder xml = new XMLEncoder(System.out)) {
xml.writeObject(path);
}
这会生成以下XML:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_60" class="java.beans.XMLDecoder">
<object class="java.awt.geom.Path2D$Float">
<void property="windingRule">
<int>0</int>
</void>
</object>
</java>
不是很好,但也不是太糟糕。我们只是错过了路径数据。所以我们只需要扩展DefaultPersistenceDelegate
以将所需的路径命令添加到输出中。
public class Path2DPersistenceDelegate extends DefaultPersistenceDelegate {
@Override
protected void initialize(Class<?> cls, Object oldInstance, Object newInstance, Encoder out) {
super.initialize(cls, oldInstance, newInstance, out);
Shape shape = (Shape) oldInstance;
float coords[] = new float[6];
Float pnt0[] = new Float[0];
Float pnt1[] = new Float[2];
Float pnt2[] = new Float[4];
Float pnt3[] = new Float[6];
Float pnts[];
PathIterator iterator = shape.getPathIterator(null);
while (!iterator.isDone()) {
int type = iterator.currentSegment(coords);
String cmd;
switch (type) {
case PathIterator.SEG_CLOSE:
cmd = "closePath";
pnts = pnt0;
break;
case PathIterator.SEG_MOVETO:
cmd = "moveTo";
pnts = pnt1;
break;
case PathIterator.SEG_LINETO:
cmd = "lineTo";
pnts = pnt1;
break;
case PathIterator.SEG_QUADTO:
cmd = "quadTo";
pnts = pnt2;
break;
case PathIterator.SEG_CUBICTO:
cmd = "curveTo";
pnts = pnt3;
break;
default:
throw new IllegalStateException("Unexpected segment type: " + type);
}
for (int i = 0; i < pnts.length; i++) {
pnts[i] = coords[i];
}
out.writeStatement(new Statement(oldInstance, cmd, pnts));
iterator.next();
}
}
}
然后,我们只使用XMLEncoder
注册此持久性委托,它将生成此帖子顶部显示的XML。
Path2DPersistenceDelegate path2d_delegate = new Path2DPersistenceDelegate();
try (XMLEncoder xml = new XMLEncoder(System.out)) {
xml.setPersistenceDelegate(Path2D.Float.class, path2d_delegate);
xml.writeObject(path);
}
由于Path2D.Float
是GeneralPath
的父类,因此GeneralPath
也会被正确编码。如果要正确编码Path2D.Double
形状,则需要修改委托以使用double
值和Double
个对象。
更新
要使用正确的Path2D.Float
属性构造windingRule
对象,而不是之后设置属性,请将以下构造函数添加到Path2DPersistenceDelegate
:
public Path2DPersistenceDelegate() {
super(new String[] { "windingRule" });
}
然后将读取XML:
...
<object class="java.awt.geom.Path2D$Float">
<int>0</int>
<void method="moveTo">
...
这会在XML中丢失一些人类可读的上下文信息;人类需要阅读文档以确定使用Path2D.Float(int)
构造函数,int
参数是windingRule
属性。
更新2:
Polygon
持久性委托非常简单:
public class PolygonPersistenceDelegate extends PersistenceDelegate {
@Override
protected Expression instantiate(Object oldInstance, Encoder out) {
Polygon polygon = (Polygon) oldInstance;
return new Expression(oldInstance, oldInstance.getClass(), "new",
new Object[] { polygon.xpoints, polygon.ypoints, polygon.npoints });
}
}
由于Area
Constructive Area Geometry对象更复杂,因此无法通过moveTo
和lineTo
类型方法创建,而只能通过添加,减去或排除或创建Shape
个对象。但构造函数采用Shape
对象,Path2D.Double
可以从Area
对象构造,因此持久代理实际上也可以非常简单地编写:
public class AreaPersistenceDelegate extends PersistenceDelegate {
@Override
protected Expression instantiate(Object oldInstance, Encoder out) {
Area area = (Area) oldInstance;
Path2D.Double p2d = new Path2D.Double(area);
return new Expression(oldInstance, oldInstance.getClass(), "new",
new Object[] { p2d });
}
}
由于我们在内部使用Path2D.Double
,我们需要将两个持久委托添加到XMLEncoder
:
try (XMLEncoder encoder = new XMLEncoder(baos)) {
encoder.setPersistenceDelegate(Area.class, new AreaPersistenceDelegate());
encoder.setPersistenceDelegate(Path2D.Double.class, new Path2DPersistenceDelegate.Double());
encoder.writeObject(area);
}
更新3:
已在GitHub上创建了PersistenceDelegate
,Area
和Path2D
GeneralPath
的项目。
注意:
Polygon
的持久性委托,因为Java 1.7似乎没有必要更新4:
对于Java 1.7,必须为每个pnts
分配new Statement()
数组;它不能分配一次并重复使用。因此,必须按如下方式更改Path2D代理:
float coords[] = new float[6];
/* Removed: Float pnt0[] = new Float[0];
Float pnt1[] = new Float[0];
Float pnt2[] = new Float[4];
Float pnt3[] = new Float[6]; */
Float pnts[];
PathIterator iterator = shape.getPathIterator(null);
while (!iterator.isDone()) {
int type = iterator.currentSegment(coords);
String cmd;
switch (type) {
case PathIterator.SEG_CLOSE:
cmd = "closePath";
pnts = new Float[0]; // Allocate for each segment
break;
case PathIterator.SEG_MOVETO:
cmd = "moveTo";
pnts = new Float[2]; // Allocate for each segment
break;
/* ... etc ...*/