switch position display

This commit is contained in:
Bane Stojkovic
2025-03-15 23:29:47 +01:00
parent 74f43159c4
commit 9d8555c4b7
15 changed files with 686 additions and 56 deletions
@@ -24,6 +24,7 @@ package org.diylc.common;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.diylc.core.IDynamicPropertySource;
import org.diylc.core.IPropertyValidator;
/**
@@ -45,9 +46,10 @@ public class PropertyWrapper implements Cloneable {
private boolean changed = false;
private int sortOrder;
private Object ownerObject;
private IDynamicPropertySource dynamicPropertySource;
public PropertyWrapper(String name, Class<?> type, String getter, String setter, boolean defaultable,
IPropertyValidator validator, int sortOrder) {
IPropertyValidator validator, int sortOrder, IDynamicPropertySource dynamicPropertySource) {
super();
this.name = name;
this.type = type;
@@ -56,6 +58,7 @@ public class PropertyWrapper implements Cloneable {
this.defaultable = defaultable;
this.validator = validator;
this.sortOrder = sortOrder;
this.dynamicPropertySource = dynamicPropertySource;
this.ownerObject = null;
}
@@ -138,6 +141,10 @@ public class PropertyWrapper implements Cloneable {
return sortOrder;
}
public IDynamicPropertySource getDynamicPropertySource() {
return dynamicPropertySource;
}
// @Override
// public Object clone() throws CloneNotSupportedException {
// // Try to invoke clone method on value if possible.
@@ -152,7 +159,7 @@ public class PropertyWrapper implements Cloneable {
public Object clone() throws CloneNotSupportedException {
PropertyWrapper clone =
new PropertyWrapper(this.name, this.type, this.getter, this.setter, this.defaultable, this.validator,
this.sortOrder);
this.sortOrder, this.dynamicPropertySource);
clone.value = this.value;
clone.changed = this.changed;
clone.unique = this.unique;
@@ -0,0 +1,34 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 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 <http://www.gnu.org/licenses/>.
*/
package org.diylc.core;
import java.util.List;
import java.util.Map;
public interface IDynamicPropertySource {
void setComponent(IDIYComponent<?> component);
List<Object> getAvailableValues();
String getDisplayValue(Object value);
}
@@ -21,6 +21,8 @@
*/
package org.diylc.core;
import java.awt.*;
/**
* Interface for all component that can act as switches. They must have at least one position.
*
@@ -28,6 +30,13 @@ package org.diylc.core;
*/
public interface ISwitch {
Color[] POLE_COLORS = new Color[] { Color.decode("#FFD1DC"),
Color.decode("#FFE5B4"),
Color.decode("#FFFACD"),
Color.decode("#B8F2E6"),
Color.decode("#AEC6CF"),
Color.decode("#E6E6FA")};
/**
* @return a positive number of positions.
*/
@@ -48,4 +57,15 @@ public interface ISwitch {
* @return true if the two points are connected in the given switch position, false otherwise.
*/
boolean arePointsConnected(int index1, int index2, int position);
/**
* @return currently selected switch position, or null if none
*/
default Integer getSelectedPosition() {
return null;
}
default Boolean getRenderConnectedTerminals() {
return false;
}
}
@@ -0,0 +1,38 @@
package org.diylc.core;
import java.util.*;
public class SwitchPositionPropertyValueSource implements IDynamicPropertySource {
private IDIYComponent<?> component;
@Override
public void setComponent(IDIYComponent<?> component) {
this.component = component;
}
@Override
public List<Object> getAvailableValues() {
if (component == null) {
throw new RuntimeException("Component not set");
}
if (!(component instanceof ISwitch sw)) {
return List.of();
}
int positionCount = sw.getPositionCount();
List<Object> res = new ArrayList<>();
res.add(null);
for (int i = 0; i < positionCount; i++) {
res.add(i);
}
return res;
}
@Override
public String getDisplayValue(Object value) {
if (value == null) {
return "";
}
return "#" + (((Integer)value) + 1);
}
}
@@ -0,0 +1,42 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 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 <http://www.gnu.org/licenses/>.
*/
package org.diylc.core.annotations;
import org.diylc.core.IDynamicPropertySource;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/***
* Used for properties having dynamic set of possible values. Only to be used in addition to
* EditableProperty annotation.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicEditableProperty {
/**
* Optional parameter for dynamic values
*
* @return
*/
Class<? extends IDynamicPropertySource> source() ;
}
@@ -25,6 +25,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.diylc.core.AllowAllValidator;
import org.diylc.core.IDynamicPropertySource;
import org.diylc.core.IPropertyValidator;
/**
@@ -41,11 +41,9 @@ import org.diylc.common.IComponentTransformer;
import org.diylc.common.PropertyWrapper;
import org.diylc.core.CreationMethod;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDynamicPropertySource;
import org.diylc.core.IPropertyValidator;
import org.diylc.core.annotations.BomPolicy;
import org.diylc.core.annotations.ComponentDescriptor;
import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.annotations.KeywordPolicy;
import org.diylc.core.annotations.*;
import org.diylc.lang.LangUtil;
/**
@@ -189,10 +187,16 @@ public class ComponentProcessor {
LangUtil.translate(o.toString());
}
}
IDynamicPropertySource dynamicPropertySource = null;
if (getter.isAnnotationPresent(DynamicEditableProperty.class)) {
DynamicEditableProperty dynamicAnnotation = getter.getAnnotation(DynamicEditableProperty.class);
dynamicPropertySource = getDynamicPropertySource(dynamicAnnotation.source());
}
PropertyWrapper property =
new PropertyWrapper(name, getter.getReturnType(), getter.getName(), setterName,
annotation.defaultable(), validator, annotation.sortOrder());
annotation.defaultable(), validator, annotation.sortOrder(), dynamicPropertySource);
properties.add(property);
}
}
@@ -278,6 +282,17 @@ public class ComponentProcessor {
return validator;
}
private IDynamicPropertySource getDynamicPropertySource(Class<? extends IDynamicPropertySource> clazz) {
IDynamicPropertySource source;
try {
source = clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
LOG.error("Could not instantiate source for " + clazz.getName(), e);
return null;
}
return source;
}
private IComponentTransformer getComponentTransformer(Class<? extends IComponentTransformer> clazz) {
if (clazz == null)
return null;
@@ -1,3 +1,24 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 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 <http://www.gnu.org/licenses/>.
*/
package org.diylc.utils;
import org.apache.log4j.Logger;
@@ -1,3 +1,24 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 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 <http://www.gnu.org/licenses/>.
*/
package org.diylc.utils;
import java.awt.Graphics2D;
@@ -0,0 +1,89 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 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 <http://www.gnu.org/licenses/>.
*/
package org.diylc.utils;
import org.diylc.core.ISwitch;
import java.util.*;
public class SwitchUtils {
public static List<Set<Integer>> getConnectedTerminals(ISwitch switchComponent, int numPoints) {
List<Set<Integer>> connectedGroups = new ArrayList<>();
// Only process if we have a selected position and should render connections
if (switchComponent.getSelectedPosition() == null || !Boolean.TRUE.equals(switchComponent.getRenderConnectedTerminals())) {
return connectedGroups;
}
// First, collect all direct connections
List<int[]> connections = new ArrayList<>();
for (int i = 0; i < numPoints; i++) {
for (int j = i + 1; j < numPoints; j++) {
if (switchComponent.arePointsConnected(i, j, switchComponent.getSelectedPosition())) {
connections.add(new int[]{i, j});
}
}
}
// Create a map to track which points are in which group
Map<Integer, Set<Integer>> pointToGroup = new HashMap<>();
// Process each connection
for (int[] connection : connections) {
int p1 = connection[0];
int p2 = connection[1];
Set<Integer> group1 = pointToGroup.get(p1);
Set<Integer> group2 = pointToGroup.get(p2);
if (group1 == null && group2 == null) {
// Create new group
Set<Integer> newGroup = new HashSet<>();
newGroup.add(p1);
newGroup.add(p2);
pointToGroup.put(p1, newGroup);
pointToGroup.put(p2, newGroup);
} else if (group1 == null) {
// Add p1 to p2's group
group2.add(p1);
pointToGroup.put(p1, group2);
} else if (group2 == null) {
// Add p2 to p1's group
group1.add(p2);
pointToGroup.put(p2, group1);
} else if (group1 != group2) {
// Merge groups
group1.addAll(group2);
for (Integer p : group2) {
pointToGroup.put(p, group1);
}
}
}
// Collect unique groups
Set<Set<Integer>> uniqueGroups = new HashSet<>(pointToGroup.values());
connectedGroups.addAll(uniqueGroups);
return connectedGroups;
}
}
@@ -0,0 +1,136 @@
package org.diylc.utils;
import org.diylc.core.ISwitch;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.*;
public class SwitchUtilsTest {
// Mock switch implementation for testing
private static class MockSwitch implements ISwitch {
private final List<int[]> connections;
private final Integer selectedPosition;
private final boolean renderConnected;
public MockSwitch(List<int[]> connections, Integer selectedPosition, boolean renderConnected) {
this.connections = connections;
this.selectedPosition = selectedPosition;
this.renderConnected = renderConnected;
}
@Override
public boolean arePointsConnected(int index1, int index2, int position) {
if (position != selectedPosition) return false;
return connections.stream()
.anyMatch(conn -> (conn[0] == index1 && conn[1] == index2) ||
(conn[0] == index2 && conn[1] == index1));
}
@Override
public Integer getSelectedPosition() {
return selectedPosition;
}
@Override
public Boolean getRenderConnectedTerminals() {
return renderConnected;
}
// Other interface methods not needed for testing
@Override public int getPositionCount() { return 0; }
@Override public String getPositionName(int position) { return null; }
}
@Test
public void testNoConnections() {
MockSwitch mockSwitch = new MockSwitch(Collections.emptyList(), 0, true);
List<Set<Integer>> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5);
assertTrue("Should return empty list when no connections", groups.isEmpty());
}
@Test
public void testSingleConnection() {
List<int[]> connections = Arrays.asList(new int[]{0, 1});
MockSwitch mockSwitch = new MockSwitch(connections, 0, true);
List<Set<Integer>> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5);
assertEquals("Should have one group", 1, groups.size());
assertTrue("Group should contain points 0 and 1",
groups.get(0).containsAll(Arrays.asList(0, 1)));
}
@Test
public void testTransitiveConnections() {
// Test case where 1-2-3 are connected through 2
List<int[]> connections = Arrays.asList(
new int[]{1, 2},
new int[]{2, 3}
);
MockSwitch mockSwitch = new MockSwitch(connections, 0, true);
List<Set<Integer>> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5);
assertEquals("Should have one group", 1, groups.size());
assertTrue("Group should contain points 1, 2, and 3",
groups.get(0).containsAll(Arrays.asList(1, 2, 3)));
}
@Test
public void testMultipleGroups() {
// Test case with two separate groups: {1,2,3} and {4,6,7}
List<int[]> connections = Arrays.asList(
new int[]{2, 3},
new int[]{1, 3},
new int[]{4, 7},
new int[]{4, 6}
);
MockSwitch mockSwitch = new MockSwitch(connections, 0, true);
List<Set<Integer>> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 8);
assertEquals("Should have two groups", 2, groups.size());
assertTrue("Groups should contain {1,2,3} and {4,6,7}",
groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(1, 2, 3))) &&
groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(4, 6, 7))));
}
@Test
public void testRenderConnectedFalse() {
List<int[]> connections = Arrays.asList(new int[]{0, 1});
MockSwitch mockSwitch = new MockSwitch(connections, 0, false);
List<Set<Integer>> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5);
assertTrue("Should return empty list when renderConnected is false", groups.isEmpty());
}
@Test
public void testNoSelectedPosition() {
List<int[]> connections = Arrays.asList(new int[]{0, 1});
MockSwitch mockSwitch = new MockSwitch(connections, null, true);
List<Set<Integer>> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5);
assertTrue("Should return empty list when no position selected", groups.isEmpty());
}
@Test
public void testComplexConnections() {
// Test case with multiple interconnected points
List<int[]> connections = Arrays.asList(
new int[]{0, 1},
new int[]{1, 2},
new int[]{2, 3},
new int[]{3, 0}, // Creates a circle
new int[]{4, 5},
new int[]{5, 6},
new int[]{7, 8} // Separate pair
);
MockSwitch mockSwitch = new MockSwitch(connections, 0, true);
List<Set<Integer>> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 9);
assertEquals("Should have three groups", 3, groups.size());
assertTrue("Should have group with points 0,1,2,3",
groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(0, 1, 2, 3))));
assertTrue("Should have group with points 4,5,6",
groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(4, 5, 6))));
assertTrue("Should have group with points 7,8",
groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(7, 8))));
}
}