//------------------------------------------------------------------------------------------------// // // // J a x b // // // //------------------------------------------------------------------------------------------------// // <editor-fold defaultstate="collapsed" desc="hdr"> // // Copyright © Audiveris 2018. All rights reserved. // // This program is free software: you can redistribute it and/or modify it under the terms of the // GNU Affero General Public License as published by the Free Software Foundation, either version // 3 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License along with this // program. If not, see <http://www.gnu.org/licenses/>. //------------------------------------------------------------------------------------------------// // </editor-fold> package org.audiveris.omr.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.CubicCurve2D; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import static java.nio.file.StandardOpenOption.CREATE; import java.text.DecimalFormat; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; /** * Class {@code Jaxb} provides a collection of type adapters and facades for JAXB. * * @author Hervé Bitteur */ public abstract class Jaxb { private static final Logger logger = LoggerFactory.getLogger(Jaxb.class); /** Not meant to be instantiated. */ private Jaxb () { } //---------// // marshal // //---------// /** * Marshal an object to a file, using provided JAXB context. * * @param object instance to marshal * @param path target file * @param jaxbContext proper context * @throws IOException on IO error * @throws JAXBException on JAXB error * @throws XMLStreamException on XML error */ public static void marshal (Object object, Path path, JAXBContext jaxbContext) throws IOException, JAXBException, XMLStreamException { try (OutputStream os = Files.newOutputStream(path, CREATE);) { Marshaller m = jaxbContext.createMarshaller(); XMLStreamWriter writer = new IndentingXMLStreamWriter( XMLOutputFactory.newInstance().createXMLStreamWriter(os, "UTF-8")); m.marshal(object, writer); os.flush(); } } //---------// // marshal // //---------// /** * Marshal an object to a stream, using provided JAXB context. * * @param object instance to marshal * @param os output stream, not closed by this method * @param jaxbContext proper context * @throws JAXBException on JAXB error * @throws XMLStreamException on XML error */ public static void marshal (Object object, OutputStream os, JAXBContext jaxbContext) throws JAXBException, XMLStreamException { Marshaller m = jaxbContext.createMarshaller(); XMLStreamWriter writer = new IndentingXMLStreamWriter( XMLOutputFactory.newInstance().createXMLStreamWriter(os, "UTF-8")); m.marshal(object, writer); } //-----------// // unmarshal // //-----------// /** * Unmarshal an object from a file, using provided JAXB context. * * @param path input file * @param jaxbContext proper context * @return the unmarshalled object * @throws IOException on IO error * @throws JAXBException on JAXB error */ public static Object unmarshal (Path path, JAXBContext jaxbContext) throws IOException, JAXBException { InputStream is = null; try { Unmarshaller um = jaxbContext.createUnmarshaller(); is = Files.newInputStream(path, StandardOpenOption.READ); return um.unmarshal(is); } finally { if (is != null) { is.close(); } } } //----------------------// // AtomicIntegerAdapter // //----------------------// /** * Adapter for AtomicInteger. */ public static class AtomicIntegerAdapter extends XmlAdapter<Integer, AtomicInteger> { @Override public Integer marshal (AtomicInteger atomic) throws Exception { return atomic.get(); } @Override public AtomicInteger unmarshal (Integer i) throws Exception { return new AtomicInteger(i); } } //------------------------// // BooleanPositiveAdapter // //------------------------// /** * Adapter for Boolean, by which only true value is marshalled into the output, * false value is not marshalled. */ public static class BooleanPositiveAdapter extends XmlAdapter<String, Boolean> { private static final String TRUE = Boolean.toString(true); @Override public String marshal (Boolean b) throws Exception { if (b == null) { return null; } return b ? TRUE : null; } @Override public Boolean unmarshal (String s) throws Exception { if (s == null) { return false; } return Boolean.parseBoolean(s); } } //--------------// // CubicAdapter // //--------------// /** * Adapter for Cubic. */ public static class CubicAdapter extends XmlAdapter<CubicAdapter.CubicFacade, CubicCurve2D> { @Override public CubicFacade marshal (CubicCurve2D curve) throws Exception { if (curve == null) { return null; } return new CubicFacade(curve); } @Override public CubicCurve2D unmarshal (CubicFacade facade) throws Exception { if (facade == null) { return null; } return facade.getCurve(); } @XmlRootElement private static class CubicFacade { @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double x1; @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double y1; @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double ctrlx1; @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double ctrly1; @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double ctrlx2; @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double ctrly2; @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double x2; @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double y2; // Needed for JAXB CubicFacade () { } CubicFacade (CubicCurve2D curve) { this.x1 = curve.getX1(); this.y1 = curve.getY1(); this.ctrlx1 = curve.getCtrlX1(); this.ctrly1 = curve.getCtrlY1(); this.ctrlx2 = curve.getCtrlX2(); this.ctrly2 = curve.getCtrlY2(); this.x2 = curve.getX2(); this.y2 = curve.getY2(); } public CubicCurve2D getCurve () { return new CubicCurve2D.Double(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2); } } } //------------------// // DimensionAdapter // //------------------// /** * Adapter for Dimension. */ public static class DimensionAdapter extends XmlAdapter<DimensionAdapter.DimensionFacade, Dimension> { @Override public DimensionFacade marshal (Dimension dim) throws Exception { if (dim == null) { return null; } return new DimensionFacade(dim); } @Override public Dimension unmarshal (DimensionFacade facade) throws Exception { if (facade == null) { return null; } return facade.getDimension(); } private static class DimensionFacade { @XmlAttribute(name = "w") public int width; @XmlAttribute(name = "h") public int height; /** * Needed for JAXB. */ DimensionFacade () { } /** * Creates a new DimensionFacade object. * * @param dimension the interfaced dimension */ DimensionFacade (Dimension dimension) { width = dimension.width; height = dimension.height; } public Dimension getDimension () { return new Dimension(width, height); } } } //----------------// // Double1Adapter // //----------------// /** * Adapter for Double, with maximum 1 decimal. */ public static class Double1Adapter extends XmlAdapter<String, Double> { private static final DecimalFormat decimal = new DecimalFormat(); static { decimal.setGroupingUsed(false); decimal.setMaximumFractionDigits(1); // For a maximum of 1 decimal } @Override public String marshal (Double d) throws Exception { if (d == null) { return null; } return decimal.format(d); } @Override public Double unmarshal (String s) throws Exception { if (s == null) { return null; } return Double.valueOf(s); } } //----------------// // Double3Adapter // //----------------// /** * Adapter for Double, with maximum 3 decimals. */ public static class Double3Adapter extends XmlAdapter<String, Double> { private static final DecimalFormat decimal = new DecimalFormat(); static { decimal.setGroupingUsed(false); decimal.setMaximumFractionDigits(3); // For a maximum of 3 decimals } @Override public String marshal (Double d) throws Exception { if (d == null) { return null; } return decimal.format(d); } @Override public Double unmarshal (String s) throws Exception { if (s == null) { return null; } return Double.valueOf(s); } } //----------------// // Double5Adapter // //----------------// /** * Adapter for Double, with maximum 5 decimals. */ public static class Double5Adapter extends XmlAdapter<String, Double> { private static final DecimalFormat decimal = new DecimalFormat(); static { decimal.setGroupingUsed(false); decimal.setMaximumFractionDigits(5); // For a maximum of 5 decimals } @Override public String marshal (Double d) throws Exception { if (d == null) { return null; } return decimal.format(d); } @Override public Double unmarshal (String s) throws Exception { if (s == null) { return null; } return Double.valueOf(s); } } //---------------// // Line2DAdapter // //---------------// /** * Adapter for Line2D. */ public static class Line2DAdapter extends XmlAdapter<Line2DAdapter.Line2DFacade, Line2D> { @Override public Line2DFacade marshal (Line2D line) throws Exception { if (line == null) { return null; } return new Line2DFacade(line); } @Override public Line2D unmarshal (Line2DFacade facade) throws Exception { if (facade == null) { return null; } return facade.getLine(); } @XmlRootElement private static class Line2DFacade { @XmlElement public Point2DFacade p1; @XmlElement public Point2DFacade p2; Line2DFacade () { } Line2DFacade (Line2D line) { Objects.requireNonNull(line, "Cannot create Line2DFacade with a null line"); p1 = new Point2DFacade(line.getP1()); p2 = new Point2DFacade(line.getP2()); } public Line2D getLine () { return new Line2D.Double(p1.x, p1.y, p2.x, p2.y); } @Override public String toString () { final StringBuilder sb = new StringBuilder("Line2DF{"); if (p1 != null) { sb.append("p1:").append(p1); } if (p2 != null) { sb.append(",p2:").append(p2); } sb.append('}'); return sb.toString(); } } } //---------------// // MarshalLogger // //---------------// /** * A logger specific for marshalling. */ public static class MarshalLogger extends Marshaller.Listener { @Override public void afterMarshal (Object source) { logger.info("GL afterMarshal {}", source); } @Override public void beforeMarshal (Object source) { logger.info("GL beforeMarshal {}", source); } } //-------------// // PathAdapter // //-------------// /** * Adapter for Path interface. */ public static class PathAdapter extends XmlAdapter<String, Path> { @Override public String marshal (Path path) throws Exception { if (path == null) { return null; } return path.toString(); } @Override public Path unmarshal (String str) { if (str == null) { return null; } return Paths.get(str); } } //----------------// // Point2DAdapter // //----------------// /** * Adapter for Point2D. */ public static class Point2DAdapter extends XmlAdapter<Point2DFacade, Point2D> { @Override public Point2DFacade marshal (Point2D point) throws Exception { if (point == null) { return null; } return new Point2DFacade(point); } @Override public Point2D unmarshal (Point2DFacade facade) throws Exception { if (facade == null) { return null; } return facade.getPoint(); } } //--------------// // PointAdapter // //--------------// /** * Adapter for Point. */ public static class PointAdapter extends XmlAdapter<PointAdapter.PointFacade, Point> { @Override public PointFacade marshal (Point point) throws Exception { if (point == null) { return null; } return new PointFacade(point); } @Override public Point unmarshal (PointFacade facade) throws Exception { if (facade == null) { return null; } return facade.getPoint(); } private static class PointFacade { @XmlAttribute public int x; @XmlAttribute public int y; // Needed for JAXB PointFacade () { } PointFacade (Point point) { this.x = point.x; this.y = point.y; } public Point getPoint () { return new Point(x, y); } @Override public String toString () { final StringBuilder sb = new StringBuilder("PointF{"); sb.append("x:").append(x); sb.append(",y:").append(y); sb.append('}'); return sb.toString(); } } } //--------------------// // Rectangle2DAdapter // //--------------------// /** * Adapter for Rectangle2D. */ public static class Rectangle2DAdapter extends XmlAdapter<Rectangle2DAdapter.Rectangle2DFacade, Rectangle2D> { @Override public Rectangle2DFacade marshal (Rectangle2D rect) throws Exception { if (rect == null) { return null; } return new Rectangle2DFacade(rect); } @Override public Rectangle2D unmarshal (Rectangle2DFacade facade) throws Exception { if (facade == null) { return null; } return facade.getRectangle2D(); } private static class Rectangle2DFacade { @XmlAttribute(name = "x") @XmlJavaTypeAdapter(type = double.class, value = Double3Adapter.class) public double x; @XmlAttribute(name = "y") @XmlJavaTypeAdapter(type = double.class, value = Double3Adapter.class) public double y; @XmlAttribute(name = "w") @XmlJavaTypeAdapter(type = double.class, value = Double3Adapter.class) public double width; @XmlAttribute(name = "h") @XmlJavaTypeAdapter(type = double.class, value = Double3Adapter.class) public double height; Rectangle2DFacade (Rectangle2D rect) { x = rect.getX(); y = rect.getY(); width = rect.getWidth(); height = rect.getHeight(); } private Rectangle2DFacade () { } public Rectangle2D getRectangle2D () { return new Rectangle2D.Double(x, y, width, height); } } } //------------------// // RectangleAdapter // //------------------// /** * Adapter for Rectangle. */ public static class RectangleAdapter extends XmlAdapter<RectangleAdapter.RectangleFacade, Rectangle> { @Override public RectangleFacade marshal (Rectangle rect) throws Exception { if (rect == null) { return null; } return new RectangleFacade(rect); } @Override public Rectangle unmarshal (RectangleFacade facade) throws Exception { if (facade == null) { return null; } return facade.getRectangle(); } private static class RectangleFacade { @XmlAttribute public int x; @XmlAttribute public int y; @XmlAttribute(name = "w") public int width; @XmlAttribute(name = "h") public int height; RectangleFacade () { } RectangleFacade (Rectangle rect) { x = rect.x; y = rect.y; width = rect.width; height = rect.height; } public Rectangle getRectangle () { return new Rectangle(x, y, width, height); } @Override public String toString () { final StringBuilder sb = new StringBuilder("RectangleF{"); sb.append("x:").append(x); sb.append(",y:").append(y); sb.append(",w:").append(width); sb.append(",h:").append(height); sb.append('}'); return sb.toString(); } } } //----------------------// // StringIntegerAdapter // //----------------------// /** * Adapter for Integer, which gives String value mandatory for @XmlID support. */ public static class StringIntegerAdapter extends XmlAdapter<String, Integer> { @Override public String marshal (Integer i) throws Exception { if (i == null) { return null; } return Integer.toString(i); } @Override public Integer unmarshal (String s) throws Exception { if (s == null) { return null; } return Integer.decode(s); } } //-----------------// // UnmarshalLogger // //-----------------// /** * Specific logger for unmarshalling. */ public static class UnmarshalLogger extends Unmarshaller.Listener { @Override public void afterUnmarshal (Object target, Object parent) { logger.info("GL afterUnmarshal parent:{} of {}", parent, target); } @Override public void beforeUnmarshal (Object target, Object parent) { logger.info("GL beforeUnmarshal parent:{} for target {}", parent, target.getClass()); } } //---------------// // Point2DFacade // //---------------// /** * A facade to Point2D. */ @XmlRootElement private static class Point2DFacade { @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double x; @XmlAttribute @XmlJavaTypeAdapter(type = double.class, value = Double1Adapter.class) public double y; Point2DFacade () { } Point2DFacade (Point2D point) { this.x = point.getX(); this.y = point.getY(); } public Point2D getPoint () { return new Point2D.Double(x, y); } @Override public String toString () { final StringBuilder sb = new StringBuilder("Point2DF{"); sb.append("x:").append(x); sb.append(",y:").append(y); sb.append('}'); return sb.toString(); } } }