diff --git a/diylc/diylc-core/src/main/java/org/diylc/serialization/ISerializationAliasProvider.java b/diylc/diylc-core/src/main/java/org/diylc/serialization/ISerializationAliasProvider.java new file mode 100644 index 00000000..c522e509 --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/serialization/ISerializationAliasProvider.java @@ -0,0 +1,44 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2025 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ +package org.diylc.serialization; + +import com.thoughtworks.xstream.XStream; + +import org.diylc.core.annotations.Indexed; + +/** + * Registers XStream aliases for classes and fields that were renamed after they shipped, so that + * the old names keep resolving. Implementations live next to the classes they alias and are + * applied to every {@link XStream} instance prepared by {@link ProjectFileManager#configure(XStream)}. + * + * @author Branislav Stojkovic + */ +@Indexed +public interface ISerializationAliasProvider { + + /** + * Registers aliases on the provided {@link XStream} instance. + * + * @param xStream + */ + void registerAliases(XStream xStream); +} diff --git a/diylc/diylc-core/src/main/java/org/diylc/serialization/ProjectFileManager.java b/diylc/diylc-core/src/main/java/org/diylc/serialization/ProjectFileManager.java index ac107343..028cf348 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/serialization/ProjectFileManager.java +++ b/diylc/diylc-core/src/main/java/org/diylc/serialization/ProjectFileManager.java @@ -133,6 +133,14 @@ public class ProjectFileManager { xStream.addImmutableType(org.diylc.core.measures.Inductance.class, true); xStream.addImmutableType(org.diylc.core.measures.Size.class, true); xStream.addPermission(AnyTypePermission.ANY); + try { + for (ISerializationAliasProvider provider : ReflectionUtils + .getIndexedInstances(ISerializationAliasProvider.class)) { + provider.registerAliases(xStream); + } + } catch (ReflectiveOperationException e) { + LOG.error("Could not register serialization aliases", e); + } } public void startNewFile() { diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java b/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java index 373769f2..5701ef43 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java @@ -79,7 +79,9 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen protected Color leadColor = LEAD_COLOR; protected Display display = Display.NAME; private boolean flipStanding = false; - private LabelOriantation labelOriantation = LabelOriantation.Directional; + @Deprecated + private LabelOrientation labelOriantation; + private LabelOrientation labelOrientation = LabelOrientation.Directional; protected boolean moveLabel = false; // parameters for adjusting the label control point @@ -361,13 +363,13 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen if (getMoveLabel()) { g2d.setTransform(oldTransform); g2d.translate(getPoints()[2].getX(), getPoints()[2].getY()); - if (getLabelOriantation() != LabelOriantation.Horizontal) { + if (getLabelOrientation() != LabelOrientation.Horizontal) { g2d.rotate(theta); } StringUtils.drawCenteredText(g2d, label, offset, 0, HorizontalAlignment.CENTER, VerticalAlignment.CENTER); g2d.setTransform(oldTransform); } else { - if (isStanding() || getLabelOriantation() == LabelOriantation.Horizontal) { + if (isStanding() || getLabelOrientation() == LabelOrientation.Horizontal) { g2d.setTransform(oldTransform); double x = (getPoints()[0].getX() + getPoints()[1].getX() - length) / 2.0; double y = (getPoints()[0].getY() + getPoints()[1].getY() - width) / 2.0; @@ -381,7 +383,7 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen } drawingObserver.stopTracking(); } -// if (getLabelOriantation() == LabelOriantation.Horizontal) { +// if (getLabelOrientation() == LabelOrientation.Horizontal) { // // drawCenteredText(g2d, label, getNewPoints()[2].getX(), getNewPoints()[2].getX(), HorizontalAlignment.CENTER, VerticalAlignment.CENTER); // g2d.drawString(label, (int) (x + (length - textRect.getWidth()) / 2 + offset), @@ -698,14 +700,20 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen } @EditableProperty(name = "Label Orientation") - public LabelOriantation getLabelOriantation() { - if (labelOriantation == null) - labelOriantation = LabelOriantation.Directional; - return labelOriantation; + public LabelOrientation getLabelOrientation() { + if (labelOrientation == null) { + if (labelOriantation != null) { + labelOrientation = labelOriantation; + labelOriantation = null; + } else { + labelOrientation = LabelOrientation.Directional; + } + } + return labelOrientation; } - public void setLabelOriantation(LabelOriantation labelOriantation) { - this.labelOriantation = labelOriantation; + public void setLabelOrientation(LabelOrientation labelOrientation) { + this.labelOrientation = labelOrientation; } @Override @@ -785,7 +793,7 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen this.hideShortLeads = hideShortLeads; } - public enum LabelOriantation { + public enum LabelOrientation { Directional, Horizontal } } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java index b65b1e41..a8a7dfe6 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/CopperTrace.java @@ -187,9 +187,9 @@ public class CopperTrace extends AbstractLeadedComponent implements ILayer @Deprecated @Override - public LabelOriantation getLabelOriantation() { + public LabelOrientation getLabelOrientation() { // TODO Auto-generated method stub - return super.getLabelOriantation(); + return super.getLabelOrientation(); } @Deprecated diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Jumper.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Jumper.java index 3e2dfbbf..2c9f8d37 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Jumper.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Jumper.java @@ -163,9 +163,9 @@ public class Jumper extends AbstractLeadedComponent implements IContinuity @Deprecated @Override - public LabelOriantation getLabelOriantation() { + public LabelOrientation getLabelOrientation() { // TODO Auto-generated method stub - return super.getLabelOriantation(); + return super.getLabelOrientation(); } @Override diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Line.java b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Line.java index ad8203fe..1779870d 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Line.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/connectivity/Line.java @@ -291,7 +291,7 @@ public class Line extends AbstractLeadedComponent { @Deprecated @Override - public org.diylc.components.AbstractLeadedComponent.LabelOriantation getLabelOriantation() { - return super.getLabelOriantation(); + public org.diylc.components.AbstractLeadedComponent.LabelOrientation getLabelOrientation() { + return super.getLabelOrientation(); } } diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/passive/Resistor.java b/diylc/diylc-library/src/main/java/org/diylc/components/passive/Resistor.java index c6c9d2aa..2a4a2c11 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/passive/Resistor.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/passive/Resistor.java @@ -197,7 +197,7 @@ public class Resistor extends AbstractLeadedComponent implements IGe @Override protected int getLabelOffset(int bodyLength, int bodyWidth, int labelLength) { - if (value == null || getColorCode() == ResistorColorCode.NONE || getLabelOriantation() != AbstractLeadedComponent.LabelOriantation.Directional) + if (value == null || getColorCode() == ResistorColorCode.NONE || getLabelOrientation() != AbstractLeadedComponent.LabelOrientation.Directional) return 0; Color[] bands = value.getColorCode(colorCode); diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java b/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java index e4c8296b..db3a16bf 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/semiconductors/BridgeRectifier.java @@ -37,7 +37,7 @@ import org.diylc.awt.StringUtils; import org.diylc.common.*; import org.diylc.components.AbstractLabeledComponent; import org.diylc.components.RoundedPolygon; -import org.diylc.components.AbstractLeadedComponent.LabelOriantation; +import org.diylc.components.AbstractLeadedComponent.LabelOrientation; import org.diylc.core.ComponentState; import org.diylc.core.IDIYComponent; import org.diylc.core.IDrawingObserver; @@ -107,7 +107,9 @@ public class BridgeRectifier extends AbstractLabeledComponent implements private Voltage voltage; private Current current; transient private Area[] body; - private LabelOriantation labelOriantation = LabelOriantation.Directional; + @Deprecated + private LabelOrientation labelOriantation; + private LabelOrientation labelOrientation = LabelOrientation.Directional; public BridgeRectifier() { super(); @@ -457,7 +459,7 @@ public class BridgeRectifier extends AbstractLabeledComponent implements AffineTransform oldTransform = g2d.getTransform(); - if (getLabelOriantation() == LabelOriantation.Directional + if (getLabelOrientation() == LabelOrientation.Directional && (getOrientation() == Orientation.DEFAULT || getOrientation() == Orientation._180)) { double centerX = bounds.getX() + bounds.width / 2; double centerY = bounds.getY() + bounds.height / 2; @@ -671,14 +673,20 @@ public class BridgeRectifier extends AbstractLabeledComponent implements } @EditableProperty(name = "Label Orientation") - public LabelOriantation getLabelOriantation() { - if (labelOriantation == null) - labelOriantation = LabelOriantation.Directional; - return labelOriantation; + public LabelOrientation getLabelOrientation() { + if (labelOrientation == null) { + if (labelOriantation != null) { + labelOrientation = labelOriantation; + labelOriantation = null; + } else { + labelOrientation = LabelOrientation.Directional; + } + } + return labelOrientation; } - public void setLabelOriantation(LabelOriantation labelOriantation) { - this.labelOriantation = labelOriantation; + public void setLabelOrientation(LabelOrientation labelOrientation) { + this.labelOrientation = labelOrientation; } @Override diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/shapes/TapeMeasure.java b/diylc/diylc-library/src/main/java/org/diylc/components/shapes/TapeMeasure.java index c0867e68..d91f13fe 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/shapes/TapeMeasure.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/shapes/TapeMeasure.java @@ -344,8 +344,8 @@ public class TapeMeasure extends AbstractLeadedComponent { @Deprecated @Override - public org.diylc.components.AbstractLeadedComponent.LabelOriantation getLabelOriantation() { - return org.diylc.components.AbstractLeadedComponent.LabelOriantation.Directional; + public org.diylc.components.AbstractLeadedComponent.LabelOrientation getLabelOrientation() { + return org.diylc.components.AbstractLeadedComponent.LabelOrientation.Directional; } @Deprecated diff --git a/diylc/diylc-library/src/main/java/org/diylc/serialization/ComponentAliasProvider.java b/diylc/diylc-library/src/main/java/org/diylc/serialization/ComponentAliasProvider.java new file mode 100644 index 00000000..ed83a411 --- /dev/null +++ b/diylc/diylc-library/src/main/java/org/diylc/serialization/ComponentAliasProvider.java @@ -0,0 +1,41 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2025 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ +package org.diylc.serialization; + +import com.thoughtworks.xstream.XStream; + +import org.diylc.components.AbstractLeadedComponent; + +public class ComponentAliasProvider implements ISerializationAliasProvider { + + @Override + public void registerAliases(XStream xStream) { + // the enum shipped misspelled and its class name is saved wherever the value is held as an + // Object (variants, defaults in config.xml); configs from before the package alias carry the + // full name. The alias registered last is also the one written, so files stay readable by + // older builds + xStream.alias("org.diylc.components.AbstractLeadedComponent$LabelOriantation", + AbstractLeadedComponent.LabelOrientation.class); + xStream.alias("diylc.AbstractLeadedComponent$LabelOriantation", + AbstractLeadedComponent.LabelOrientation.class); + } +} diff --git a/diylc/diylc-library/src/test/java/org/diylc/serialization/ComponentAliasProviderTest.java b/diylc/diylc-library/src/test/java/org/diylc/serialization/ComponentAliasProviderTest.java new file mode 100644 index 00000000..04d09631 --- /dev/null +++ b/diylc/diylc-library/src/test/java/org/diylc/serialization/ComponentAliasProviderTest.java @@ -0,0 +1,115 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2025 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ +package org.diylc.serialization; + +import java.util.HashMap; +import java.util.Map; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; + +import org.diylc.components.AbstractLeadedComponent.LabelOrientation; +import org.diylc.components.passive.Resistor; +import org.diylc.components.semiconductors.BridgeRectifier; +import org.junit.Assert; +import org.junit.Test; + +/** + * Files saved before {@code LabelOriantation} was renamed must keep loading. The field is migrated + * and saved under its new name; the enum keeps its shipped class name on disk, because older builds + * discard a whole config file that names a class they cannot resolve. + */ +public class ComponentAliasProviderTest { + + private static final String OLD_ENUM_NAME = "diylc.AbstractLeadedComponent_-LabelOriantation"; + private static final String OLD_FULL_ENUM_NAME = + "org.diylc.components.AbstractLeadedComponent_-LabelOriantation"; + + // a fresh instance has not seen any class yet, which is the state it is in while reading + // config.xml at startup + private static XStream createXStream() { + XStream xStream = new XStream(new DomDriver("UTF-8")); + ProjectFileManager.configure(xStream); + return xStream; + } + + // files saved before the rename carry only the misspelled field + private static String toOldFieldName(String xml) { + Assert.assertTrue(xml.contains("labelOrientation")); + return xml.replace("labelOrientation", "labelOriantation"); + } + + @Test + public void leadedComponentMigratesOldFieldName() { + Resistor resistor = new Resistor(); + resistor.setLabelOrientation(LabelOrientation.Horizontal); + String oldXml = toOldFieldName(createXStream().toXML(resistor)); + + Resistor loaded = (Resistor) createXStream().fromXML(oldXml); + Assert.assertEquals(LabelOrientation.Horizontal, loaded.getLabelOrientation()); + + String savedXml = createXStream().toXML(loaded); + Assert.assertTrue(savedXml.contains("Horizontal")); + Assert.assertFalse(savedXml.contains("labelOriantation")); + } + + @Test + public void bridgeRectifierMigratesOldFieldName() { + BridgeRectifier rectifier = new BridgeRectifier(); + rectifier.setLabelOrientation(LabelOrientation.Horizontal); + String oldXml = toOldFieldName(createXStream().toXML(rectifier)); + + BridgeRectifier loaded = (BridgeRectifier) createXStream().fromXML(oldXml); + Assert.assertEquals(LabelOrientation.Horizontal, loaded.getLabelOrientation()); + + String savedXml = createXStream().toXML(loaded); + Assert.assertTrue(savedXml.contains("Horizontal")); + Assert.assertFalse(savedXml.contains("labelOriantation")); + } + + @Test + public void enumValueHeldAsObjectReadsOldClassName() { + String xml = "Label Orientation<" + OLD_ENUM_NAME + + ">Horizontal"; + + Map loaded = (Map) createXStream().fromXML(xml); + Assert.assertEquals(LabelOrientation.Horizontal, loaded.get("Label Orientation")); + } + + @Test + public void enumValueHeldAsObjectReadsOldFullClassName() { + String xml = "Label Orientation<" + OLD_FULL_ENUM_NAME + + ">Horizontal"; + + Map loaded = (Map) createXStream().fromXML(xml); + Assert.assertEquals(LabelOrientation.Horizontal, loaded.get("Label Orientation")); + } + + @Test + public void enumValueHeldAsObjectWritesOldClassName() { + Map values = new HashMap(); + values.put("Label Orientation", LabelOrientation.Horizontal); + + String xml = createXStream().toXML(values); + Assert.assertTrue(xml.contains("<" + OLD_ENUM_NAME + ">Horizontal")); + } +}