fixed an ugly bug concerning mutable key default values

This commit is contained in:
hneemann 2019-08-25 09:55:44 +02:00
parent e0ebd0e0ae
commit e59be72194
16 changed files with 320 additions and 177 deletions

View File

@ -28,9 +28,9 @@
<sodipodi:namedview
showgrid="true"
id="namedview4"
inkscape:zoom="3.5454545"
inkscape:cx="71.064102"
inkscape:cy="87.435899"
inkscape:zoom="3.5090909"
inkscape:cx="100.47927"
inkscape:cy="110"
inkscape:window-width="1680"
inkscape:window-height="993"
inkscape:window-x="0"
@ -45,7 +45,7 @@
id="grid2" />
</sodipodi:namedview>
<path
style="fill:#ffffb4;fill-opacity:0.78431373;stroke:#000000;stroke-width:3"
style="fill:#ffffb4;fill-opacity:0.78431373;stroke:#000000;stroke-width:4"
d="M 0,-30 80,10 V 90 L 0,130 V 60 L 30,50 0,40 Z"
id="rect6"
inkscape:connector-curvature="0"
@ -59,7 +59,7 @@
<circle
style="fill:#0000b2"
r="3"
cy="100.10256"
cy="79.794868"
cx="0.28205127"
id="pin+:B" />
<circle
@ -71,7 +71,7 @@
<circle
style="fill:#0000b2"
r="3"
cy="120.07693"
cy="99.769234"
cx="-0.2820513"
id="pin+:Ci" />
<circle

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -17,20 +17,22 @@ import java.io.File;
public class Key<VALUE> {
private final String key;
private final VALUE def;
private final Default<VALUE> defFactory;
private final String langKey;
private boolean groupEditAllowed = false;
private Key dependsOn;
private CheckEnabled checkEnabled;
private boolean isSecondary;
private boolean requiresRestart = false;
// Both values are always null in digital.
// Both are only used within a custom implemented component.
private String name;
private String description;
private boolean isSecondary;
private boolean requiresRestart = false;
/**
* Creates a new Key
* Creates a new Key.
* Use this constructor only if the def value is not mutable!
*
* @param key the key
* @param def the default value
@ -41,6 +43,23 @@ public class Key<VALUE> {
if (def == null)
throw new NullPointerException();
this.def = def;
this.defFactory = null;
}
/**
* Creates a new Key.
* Use this constructor if the def value is mutable!
*
* @param key the key
* @param defFactory the factory to create a default value
*/
public Key(String key, Default<VALUE> defFactory) {
this.key = key;
langKey = "key_" + key.replace(" ", "");
if (defFactory == null)
throw new NullPointerException();
this.def = null;
this.defFactory = defFactory;
}
/**
@ -68,14 +87,17 @@ public class Key<VALUE> {
* @return the default value of this key
*/
public VALUE getDefault() {
return def;
if (def != null)
return def;
else
return defFactory.createDefault();
}
/**
* @return The values class
*/
public Class getValueClass() {
return def.getClass();
return getDefault().getClass();
}
@Override
@ -490,4 +512,18 @@ public class Key<VALUE> {
*/
boolean isEnabled(T t);
}
/**
* Used to provide a default value if the value is mutable.
*
* @param <VALUE> the type of the value
*/
public interface Default<VALUE> {
/**
* Called to create a new default value.
*
* @return the default value
*/
VALUE createDefault();
}
}

View File

@ -278,7 +278,7 @@ public final class Keys {
* the data key for memory
*/
public static final Key<DataField> DATA
= new Key<>("Data", DataField.DEFAULT);
= new Key<>("Data", DataField::new);
/**
* flag for flipping selector pos in muxers, decoders and drivers
@ -602,7 +602,7 @@ public final class Keys {
* contains the input inverter config
*/
public static final Key<InverterConfig> INVERTER_CONFIG
= new Key<>("inverterConfig", new InverterConfig());
= new Key<>("inverterConfig", new InverterConfig.Builder().build());
/**
* Background Color of nested circuits
@ -661,7 +661,7 @@ public final class Keys {
* The manager which contains all the roms data
*/
public static final Key<ROMManger> ROMMANAGER
= new Key<>("romContent", ROMManger.EMPTY).setSecondary();
= new Key<>("romContent", ROMManger::new).setSecondary();
/**
@ -707,7 +707,7 @@ public final class Keys {
* Shape used to represent a visual element
*/
public static final Key<CustomShapeDescription> CUSTOM_SHAPE
= new Key<>("customShape", CustomShapeDescription.EMPTY)
= new Key<>("customShape", new CustomShapeDescription.Builder().build())
.setSecondary()
.setDependsOn(SHAPE_TYPE, st -> st.equals(CustomCircuitShapeType.CUSTOM));

View File

@ -17,15 +17,17 @@ import java.util.Arrays;
*/
public class DataField implements HGSArray {
/***
* Simple default data field
*/
public static final DataField DEFAULT = new DataField(0);
private long[] data;
private final transient ArrayList<DataListener> listeners = new ArrayList<>();
/**
* Creates a new DataField of size 0
*/
public DataField() {
this(0);
}
/**
* Creates a new DataField
*
@ -174,6 +176,13 @@ public class DataField implements HGSArray {
return data.length;
}
/**
* @return true if the data field is empty
*/
public boolean isEmpty() {
return trim() == 0;
}
/**
* Adds a listener to this DataField
*
@ -247,4 +256,17 @@ public class DataField implements HGSArray {
public long[] getData() {
return data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DataField dataField = (DataField) o;
return Arrays.equals(data, dataField.data);
}
@Override
public int hashCode() {
return Arrays.hashCode(data);
}
}

View File

@ -5,10 +5,7 @@
*/
package de.neemann.digital.core.memory;
import de.neemann.digital.core.Node;
import de.neemann.digital.core.NodeException;
import de.neemann.digital.core.ObservableValue;
import de.neemann.digital.core.ObservableValues;
import de.neemann.digital.core.*;
import de.neemann.digital.core.element.Element;
import de.neemann.digital.core.element.ElementAttributes;
import de.neemann.digital.core.element.ElementTypeDescription;
@ -40,6 +37,7 @@ public class EEPROM extends Node implements Element, RAMInterface, ROMInterface
private final int bits;
private final int addrBits;
private final ElementAttributes attr;
private final int size;
private final String label;
private final ObservableValue dataOut;
@ -65,6 +63,7 @@ public class EEPROM extends Node implements Element, RAMInterface, ROMInterface
*/
public EEPROM(ElementAttributes attr) {
super(true);
this.attr = attr;
bits = attr.get(Keys.BITS);
addrBits = attr.get(Keys.ADDR_BITS);
size = 1 << addrBits;
@ -77,6 +76,14 @@ public class EEPROM extends Node implements Element, RAMInterface, ROMInterface
isProgramMemory = attr.get(Keys.IS_PROGRAM_MEMORY);
}
@Override
public void registerNodes(Model model) {
super.registerNodes(model);
if (memory.isEmpty())
model.addObserver(event -> attr.set(Keys.DATA, memory), ModelEvent.STOPPED);
}
@Override
public void setInputs(ObservableValues inputs) throws NodeException {
addrIn = inputs.get(0).checkBits(addrBits, this).addObserverToValue(this);

View File

@ -5,6 +5,8 @@
*/
package de.neemann.digital.core.memory;
import de.neemann.digital.core.Model;
import de.neemann.digital.core.ModelEvent;
import de.neemann.digital.core.element.ElementAttributes;
import de.neemann.digital.core.element.ElementTypeDescription;
import de.neemann.digital.core.element.Keys;
@ -33,6 +35,9 @@ public class EEPROMDualPort extends RAMDualPort implements ROMInterface {
.addAttribute(Keys.LABEL)
.addAttribute(Keys.DATA);
private final ElementAttributes attr;
private DataField memory;
/**
* Creates a new instance
*
@ -40,11 +45,21 @@ public class EEPROMDualPort extends RAMDualPort implements ROMInterface {
*/
public EEPROMDualPort(ElementAttributes attr) {
super(attr);
this.attr = attr;
}
@Override
protected DataField createDataField(ElementAttributes attr, int size) {
return attr.get(Keys.DATA);
memory = attr.get(Keys.DATA);
return memory;
}
@Override
public void registerNodes(Model model) {
super.registerNodes(model);
if (memory.isEmpty())
model.addObserver(event -> attr.set(Keys.DATA, memory), ModelEvent.STOPPED);
}
}

View File

@ -10,15 +10,12 @@ import de.neemann.digital.core.Node;
import de.neemann.digital.core.memory.DataField;
import java.util.HashMap;
import java.util.Objects;
/**
* The Manager to manage all necessary rom images
*/
public class ROMManger {
/**
* The empty instance
*/
public static final ROMManger EMPTY = new ROMManger();
private final HashMap<String, DataField> roms;
@ -35,6 +32,8 @@ public class ROMManger {
* @param model the mode to use
*/
public void applyTo(Model model) {
if (roms == null)
return;
for (Node n : model.findNode(n -> n instanceof ROMInterface)) {
ROMInterface rom = (ROMInterface) n;
DataField data = roms.get(rom.getLabel());
@ -65,19 +64,23 @@ public class ROMManger {
roms.put(label, data);
}
/**
* @return returns EMPTY it this ROMManager is empty, this otherwise
*/
public ROMManger getMinimized() {
if (roms.isEmpty())
return EMPTY;
return this;
}
/**
* @return true if no ROM's are stored
*/
public boolean isEmpty() {
return roms.isEmpty();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ROMManger romManger = (ROMManger) o;
return Objects.equals(roms, romManger.roms);
}
@Override
public int hashCode() {
return Objects.hash(roms);
}
}

View File

@ -15,29 +15,12 @@ import java.util.Objects;
/**
* Manages the input inverting of a component
*/
public class InverterConfig implements HGSMap {
public final class InverterConfig implements HGSMap {
private HashSet<String> inputs;
/**
* Creates a new instance.
* No input is inverted.
*/
public InverterConfig() {
inputs = null;
}
/**
* Adds a signal to invert
*
* @param name the signale
* @return this for chained calls
*/
public InverterConfig add(String name) {
if (inputs == null)
inputs = new HashSet<>();
inputs.add(name);
return this;
private InverterConfig(HashSet<String> inputs) {
this.inputs = inputs;
}
/**
@ -129,4 +112,35 @@ public class InverterConfig implements HGSMap {
return inputs.contains(key);
}
/**
* Builder to create InverterConfig instances
*/
public static class Builder {
private HashSet<String> inputs;
/**
* Adds a signal to invert
*
* @param name the signale
* @return this for chained calls
*/
public Builder add(String name) {
if (inputs == null)
inputs = new HashSet<>();
inputs.add(name);
return this;
}
/**
* Creats the instance
*
* @return the created instance
*/
public InverterConfig build() {
return new InverterConfig(inputs);
}
}
}

View File

@ -212,7 +212,7 @@ public final class ShapeFactory {
return new LayoutShape(customDescr, elementAttributes);
case CUSTOM:
final CustomShapeDescription customShapeDescription = customDescr.getAttributes().get(Keys.CUSTOM_SHAPE);
if (customShapeDescription != CustomShapeDescription.EMPTY)
if (!customShapeDescription.isEmpty())
return new CustomShape(customShapeDescription, elementAttributes.getLabel(),
pt.getInputDescription(elementAttributes),
pt.getOutputDescriptions(elementAttributes));

View File

@ -24,95 +24,19 @@ import java.util.Iterator;
/**
* Is intended to be stored in a file.
*/
public class CustomShapeDescription implements Iterable<CustomShapeDescription.Holder> {
/**
* The default empty shape instance
*/
public static final CustomShapeDescription EMPTY = new CustomShapeDescription();
public final class CustomShapeDescription implements Iterable<CustomShapeDescription.Holder> {
private HashMap<String, Pin> pins;
private ArrayList<Holder> drawables;
private TextHolder label;
private final HashMap<String, Pin> pins;
private final ArrayList<Holder> drawables;
private final TextHolder label;
/**
* Creates a new instance
*/
public CustomShapeDescription() {
pins = new HashMap<>();
drawables = new ArrayList<>();
}
/**
* Adds a pin to this shape description
*
* @param name the name of the pin
* @param pos the pins position
* @param showLabel if true the label of the pin is shown
* @return this for chained calls
*/
public CustomShapeDescription addPin(String name, Vector pos, boolean showLabel) {
pins.put(name, new Pin(pos, showLabel));
return this;
}
/**
* Adds a polygon to the shape
*
* @param p1 starting point of the line
* @param p2 ending point of the line
* @param thickness the line thickness
* @param color the color to use
* @return this for chained calls
*/
public CustomShapeDescription addLine(Vector p1, Vector p2, int thickness, Color color) {
drawables.add(new LineHolder(p1, p2, thickness, color));
return this;
}
/**
* Adds a circle to the shape
*
* @param p1 upper left corner of the circles bounding box
* @param p2 lower right corner of the circles bounding box
* @param thickness the line thickness
* @param color the color to use
* @param filled true if filled
* @return this for chained calls
*/
public CustomShapeDescription addCircle(Vector p1, Vector p2, int thickness, Color color, boolean filled) {
drawables.add(new CircleHolder(p1, p2, thickness, color, filled));
return this;
}
/**
* Adds a polygon to the shape
*
* @param poly the polygon to add
* @param thickness the line thickness
* @param color the color to use
* @param filled true if filled
* @return this for chained calls
*/
public CustomShapeDescription addPolygon(Polygon poly, int thickness, Color color, boolean filled) {
drawables.add(new PolygonHolder(poly, thickness, filled, color));
return this;
}
/**
* Adds a text to the shape
*
* @param p1 position
* @param p2 second position to determin the base line orientation
* @param text the text to draw
* @param orientation the orientation of the text
* @param size the font size
* @param color the text color
* @return this for chained calls
*/
public CustomShapeDescription addText(Vector p1, Vector p2, String text, Orientation orientation, int size, Color color) {
drawables.add(new TextHolder(p1, p2, text, orientation, size, color));
return this;
private CustomShapeDescription(HashMap<String, Pin> pins, ArrayList<Holder> drawables, TextHolder label) {
this.pins = pins;
this.drawables = drawables;
this.label = label;
}
/**
@ -155,19 +79,6 @@ public class CustomShapeDescription implements Iterable<CustomShapeDescription.H
return pins.size();
}
/**
* Sets the label positioning info.
*
* @param pos0 pos0
* @param pos1 pos1
* @param textOrientation textOrientation
* @param fontSize fontSize
* @param filled filled
*/
public void setLabel(Vector pos0, Vector pos1, Orientation textOrientation, int fontSize, Color filled) {
label = new TextHolder(pos0, pos1, "", textOrientation, fontSize, filled);
}
/**
* @return the TextHolder used to draw the label, maybe null
*/
@ -182,6 +93,13 @@ public class CustomShapeDescription implements Iterable<CustomShapeDescription.H
return pins.values();
}
/**
* @return true if shape is empty
*/
public boolean isEmpty() {
return drawables.isEmpty() && pins.isEmpty();
}
/**
* Checks the compatibility of this shape to the given circuit
*
@ -201,6 +119,26 @@ public class CustomShapeDescription implements Iterable<CustomShapeDescription.H
throw new PinException(Lang.get("err_morePinsDefinedInSVGAsNeeded"));
}
/*
* Two CustomShapeDescriptions are equal if and only if they are both empty!
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomShapeDescription customShapeDescription = (CustomShapeDescription) o;
return customShapeDescription.isEmpty() && isEmpty();
}
@Override
public int hashCode() {
if (isEmpty())
return 0;
return super.hashCode();
}
private interface Transformable {
void transform(Transform tr);
}
@ -423,4 +361,117 @@ public class CustomShapeDescription implements Iterable<CustomShapeDescription.H
pos = pos.transform(tr).round();
}
}
/**
* Used to build a custom shape description
*/
public static final class Builder {
private final HashMap<String, Pin> pins;
private final ArrayList<Holder> drawables;
private TextHolder label;
/**
* Creates a new builder
*/
public Builder() {
pins = new HashMap<>();
drawables = new ArrayList<>();
}
/**
* Sets the label positioning info.
*
* @param pos0 pos0
* @param pos1 pos1
* @param textOrientation textOrientation
* @param fontSize fontSize
* @param filled filled
* @return this for chained calls
*/
public Builder setLabel(Vector pos0, Vector pos1, Orientation textOrientation, int fontSize, Color filled) {
label = new TextHolder(pos0, pos1, "", textOrientation, fontSize, filled);
return this;
}
/**
* Adds a pin to this shape description
*
* @param name the name of the pin
* @param pos the pins position
* @param showLabel if true the label of the pin is shown
* @return this for chained calls
*/
public Builder addPin(String name, Vector pos, boolean showLabel) {
pins.put(name, new Pin(pos, showLabel));
return this;
}
/**
* Adds a polygon to the shape
*
* @param p1 starting point of the line
* @param p2 ending point of the line
* @param thickness the line thickness
* @param color the color to use
* @return this for chained calls
*/
public Builder addLine(Vector p1, Vector p2, int thickness, Color color) {
drawables.add(new LineHolder(p1, p2, thickness, color));
return this;
}
/**
* Adds a circle to the shape
*
* @param p1 upper left corner of the circles bounding box
* @param p2 lower right corner of the circles bounding box
* @param thickness the line thickness
* @param color the color to use
* @param filled true if filled
* @return this for chained calls
*/
public Builder addCircle(Vector p1, Vector p2, int thickness, Color color, boolean filled) {
drawables.add(new CircleHolder(p1, p2, thickness, color, filled));
return this;
}
/**
* Adds a polygon to the shape
*
* @param poly the polygon to add
* @param thickness the line thickness
* @param color the color to use
* @param filled true if filled
* @return this for chained calls
*/
public Builder addPolygon(Polygon poly, int thickness, Color color, boolean filled) {
drawables.add(new PolygonHolder(poly, thickness, filled, color));
return this;
}
/**
* Adds a text to the shape
*
* @param p1 position
* @param p2 second position to determin the base line orientation
* @param text the text to draw
* @param orientation the orientation of the text
* @param size the font size
* @param color the text color
* @return this for chained calls
*/
public Builder addText(Vector p1, Vector p2, String text, Orientation orientation, int size, Color color) {
drawables.add(new TextHolder(p1, p2, text, orientation, size, color));
return this;
}
/**
* @return the {@link CustomShapeDescription}
*/
public CustomShapeDescription build() {
return new CustomShapeDescription(pins, drawables, label);
}
}
}

View File

@ -67,9 +67,10 @@ public class SvgImporter {
NodeList gList = svg.getElementsByTagName("svg").item(0).getChildNodes();
Context c = new Context();
try {
CustomShapeDescription csd = new CustomShapeDescription();
create(csd, gList, c);
CustomShapeDescription.Builder builder = new CustomShapeDescription.Builder();
create(builder, gList, c);
CustomShapeDescription csd = builder.build();
if (csd.getPinCount() > 0) {
float xMin = Float.MAX_VALUE;
float yMin = Float.MAX_VALUE;
@ -86,7 +87,7 @@ public class SvgImporter {
}
}
private void create(CustomShapeDescription csd, NodeList gList, Context c) throws SvgException {
private void create(CustomShapeDescription.Builder csd, NodeList gList, Context c) throws SvgException {
for (int i = 0; i < gList.getLength(); i++) {
final Node node = gList.item(i);
if (node instanceof Element) {
@ -99,7 +100,7 @@ public class SvgImporter {
}
}
private void create(CustomShapeDescription csd, Element element, Context parent) throws SvgException {
private void create(CustomShapeDescription.Builder csd, Element element, Context parent) throws SvgException {
Context c = new Context(parent, element);
switch (element.getNodeName()) {
case "a":
@ -150,19 +151,19 @@ public class SvgImporter {
}
}
private void drawTransformedPolygon(CustomShapeDescription csd, Context c, Polygon polygon) {
private void drawTransformedPolygon(CustomShapeDescription.Builder csd, Context c, Polygon polygon) {
if (polygon != null)
drawPolygon(csd, c, polygon.transform(c.getTransform()));
}
private void drawPolygon(CustomShapeDescription csd, Context c, Polygon polygon) {
private void drawPolygon(CustomShapeDescription.Builder csd, Context c, Polygon polygon) {
if (c.getFilled() != null && polygon.isClosed())
csd.addPolygon(polygon, c.getThickness(), c.getFilled(), true);
if (c.getStroke() != null)
csd.addPolygon(polygon, c.getThickness(), c.getStroke(), false);
}
private void drawRect(CustomShapeDescription csd, Element element, Context c) {
private void drawRect(CustomShapeDescription.Builder csd, Element element, Context c) {
VectorInterface size = new VectorFloat(c.getLength(element.getAttribute("width")), c.getLength(element.getAttribute("height")));
VectorInterface pos = new VectorFloat(c.getLength(element.getAttribute("x")), c.getLength(element.getAttribute("y")));
String rxStr = element.getAttribute("rx");
@ -213,7 +214,7 @@ public class SvgImporter {
drawPolygon(csd, c, polygon);
}
private void drawCircle(CustomShapeDescription csd, Element element, Context c) {
private void drawCircle(CustomShapeDescription.Builder csd, Element element, Context c) {
if (element.hasAttribute("id")) {
VectorInterface pos = c.v(c.getLength(element.getAttribute("cx")), c.getLength(element.getAttribute("cy")));
String id = element.getAttribute("id");
@ -279,7 +280,7 @@ public class SvgImporter {
return new Vector(Math.round(pos.getXFloat() / SIZE) * SIZE, Math.round(pos.getYFloat() / SIZE) * SIZE);
}
private void drawText(CustomShapeDescription csd, Context c, Element element) throws SvgException {
private void drawText(CustomShapeDescription.Builder csd, Context c, Element element) throws SvgException {
VectorFloat p = new VectorFloat(c.getLength(element.getAttribute("x")), c.getLength(element.getAttribute("y")));
VectorInterface pos0 = p.transform(c.getTransform());
VectorInterface pos1 = p.add(new VectorFloat(1, 0)).transform(c.getTransform());
@ -290,7 +291,7 @@ public class SvgImporter {
drawTextElement(csd, c, element, pos0, pos1);
}
private void drawTextElement(CustomShapeDescription csd, Context c, Element element, VectorInterface pos0, VectorInterface pos1) throws SvgException {
private void drawTextElement(CustomShapeDescription.Builder csd, Context c, Element element, VectorInterface pos0, VectorInterface pos1) throws SvgException {
NodeList nodes = element.getElementsByTagName("*");
if (nodes.getLength() == 0) {
String text = element.getTextContent();

View File

@ -52,7 +52,7 @@ public class CustomShapeEditor extends EditorFactory.LabelEditor<CustomShapeDesc
clear = new ToolTipAction(Lang.get("btn_clearData")) {
@Override
public void actionPerformed(ActionEvent e) {
customShapeDescription = CustomShapeDescription.EMPTY;
customShapeDescription = new CustomShapeDescription.Builder().build();
}
};
panel.add(clear.createJButton());

View File

@ -1010,12 +1010,12 @@ public final class EditorFactory {
}
private InverterConfig getInverterConfig() {
InverterConfig ic = new InverterConfig();
InverterConfig.Builder ic = new InverterConfig.Builder();
for (JCheckBox cb : boxes) {
if (cb.isSelected())
ic.add(cb.getText());
}
return ic;
return ic.build();
}
}

View File

@ -206,7 +206,7 @@ public class ROMEditorDialog extends JDialog {
for (RomHolder rh : romlist)
rm.addRom(rh.ri.getLabel(), rh.data);
return rm.getMinimized();
return rm;
}
}

View File

@ -17,12 +17,6 @@ import java.util.ArrayList;
* The test data.
*/
public class TestCaseDescription {
/**
* the default instance
*/
public static final TestCaseDescription DEFAULT = new TestCaseDescription("");
private String dataString;
private transient LineEmitter lines;
private transient ArrayList<String> names;

View File

@ -18,7 +18,7 @@ public class TestCaseElement implements Element {
/**
* the used {@link ElementAttributes} key
*/
public static final Key<TestCaseDescription> TESTDATA = new Key<>("Testdata", TestCaseDescription.DEFAULT);
public static final Key<TestCaseDescription> TESTDATA = new Key<>("Testdata", () -> new TestCaseDescription(""));
/**
* The TestCaseElement description