1 /*
2  * Copyright (c) 2019 Helmut Neemann.
3  * Use of this source code is governed by the GPL v3 license
4  * that can be found in the LICENSE file.
5  */
6 package de.neemann.digital.gui.components;
7 
8 import de.neemann.digital.core.element.Keys;
9 import de.neemann.digital.draw.elements.Circuit;
10 import de.neemann.digital.draw.elements.Movable;
11 import de.neemann.digital.draw.elements.VisualElement;
12 
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.Objects;
17 
18 /**
19  * Used to renumber labels if copied elements are inserted into a circuit.
20  * Modifies the elements only if a element is present in the circuit which has
21  * the same label as the element to copy.
22  */
23 public class CopiedElementLabelRenamer {
24 
25     private final ArrayList<Movable> elements;
26 
27     /**
28      * Creates a new instance
29      *
30      * @param circuit  the circuit to copy to
31      * @param elements the element to insert
32      */
CopiedElementLabelRenamer(Circuit circuit, ArrayList<Movable> elements)33     CopiedElementLabelRenamer(Circuit circuit, ArrayList<Movable> elements) {
34         this.elements = elements;
35 
36 
37         HashMap<LabelClass, PresentIndex> circuitMap = new HashMap<>();
38         for (VisualElement ve : circuit.getElements()) {
39             LabelInstance li = LabelInstance.create(ve);
40             if (li != null) {
41                 PresentIndex pi = circuitMap.computeIfAbsent(li.getLabelClass(), labelClass -> new PresentIndex());
42                 pi.add(li.getNumber());
43             }
44         }
45 
46         HashMap<LabelClass, MinIndex> insertMap = new HashMap<>();
47         for (Movable m : elements)
48             if (m instanceof VisualElement) {
49                 LabelInstance li = LabelInstance.create((VisualElement) m);
50                 if (li != null) {
51                     MinIndex mi = insertMap.get(li.getLabelClass());
52                     if (mi == null) {
53                         mi = new MinIndex(li.getNumber());
54                         insertMap.put(li.getLabelClass(), mi);
55                     } else {
56                         mi.checkMin(li.getNumber());
57                     }
58                 }
59             }
60 
61         for (Movable m : elements)
62             if (m instanceof VisualElement) {
63                 final VisualElement ve = (VisualElement) m;
64 
65                 if (!ve.getElementAttributes().get(Keys.PINNUMBER).isEmpty())
66                     continue;
67 
68                 LabelInstance li = LabelInstance.create(ve);
69                 if (li == null)
70                     continue;
71 
72                 PresentIndex pi = circuitMap.get(li.getLabelClass());
73                 if (pi == null)
74                     continue;
75 
76                 if (!pi.contains(li.getNumber()))
77                     continue;
78 
79                 int maxAvail = pi.getMax();
80                 int minToInsert = insertMap.get(li.getLabelClass()).getMin();
81 
82                 int delta = maxAvail - minToInsert + 1;
83 
84                 ve.setAttribute(Keys.LABEL, li.getLabel(delta));
85             }
86     }
87 
88     /**
89      * @return the elements with renamed labels.
90      */
rename()91     public ArrayList<Movable> rename() {
92         return elements;
93     }
94 
95     static final class LabelInstance {
96 
create(VisualElement ve)97         static LabelInstance create(VisualElement ve) {
98             return create(ve.getElementName(), ve.getElementAttributes().getLabel());
99         }
100 
create(String elementName, String fullLabel)101         static LabelInstance create(String elementName, String fullLabel) {
102             if (fullLabel == null)
103                 return null;
104 
105             int pos = fullLabel.length();
106             if (pos == 0)
107                 return null;
108 
109             if (!Character.isDigit(fullLabel.charAt(pos - 1)))
110                 return null;
111 
112             int number = 0;
113             int base = 1;
114             while (pos > 0 && Character.isDigit(fullLabel.charAt(pos - 1))) {
115                 pos--;
116                 number += (fullLabel.charAt(pos) - '0') * base;
117                 base *= 10;
118             }
119 
120             String label = fullLabel.substring(0, pos);
121             LabelClass lc = new LabelClass(elementName, label);
122 
123             return new LabelInstance(lc, number);
124         }
125 
126         private final LabelClass lc;
127         private final int number;
128 
LabelInstance(LabelClass lc, int number)129         private LabelInstance(LabelClass lc, int number) {
130             this.lc = lc;
131             this.number = number;
132         }
133 
getLabelClass()134         LabelClass getLabelClass() {
135             return lc;
136         }
137 
getNumber()138         public int getNumber() {
139             return number;
140         }
141 
getLabel(int delta)142         public String getLabel(int delta) {
143             return lc.label + Integer.toString(number + delta);
144         }
145     }
146 
147 
148     static final class LabelClass {
149         private final String elementName;
150         private final String label;
151 
LabelClass(String elementName, String label)152         private LabelClass(String elementName, String label) {
153             this.elementName = elementName;
154             this.label = label;
155         }
156 
getElementName()157         public String getElementName() {
158             return elementName;
159         }
160 
getLabel()161         public String getLabel() {
162             return label;
163         }
164 
165         @Override
equals(Object o)166         public boolean equals(Object o) {
167             if (this == o) return true;
168             if (o == null || getClass() != o.getClass()) return false;
169             LabelClass that = (LabelClass) o;
170             return Objects.equals(elementName, that.elementName)
171                     && Objects.equals(label, that.label);
172         }
173 
174         @Override
hashCode()175         public int hashCode() {
176             return Objects.hash(elementName, label);
177         }
178     }
179 
180     private static final class MinIndex {
181         private int min;
182 
MinIndex(int number)183         private MinIndex(int number) {
184             this.min = number;
185         }
186 
checkMin(int number)187         void checkMin(int number) {
188             if (this.min > number)
189                 this.min = number;
190         }
191 
getMin()192         public int getMin() {
193             return min;
194         }
195     }
196 
197     private static final class PresentIndex {
198         private HashSet<Integer> numbers;
199         private int max = Integer.MIN_VALUE;
200 
PresentIndex()201         private PresentIndex() {
202             numbers = new HashSet<>();
203         }
204 
add(int number)205         public void add(int number) {
206             numbers.add(number);
207             if (number > max)
208                 max = number;
209         }
210 
contains(int number)211         public boolean contains(int number) {
212             return numbers.contains(number);
213         }
214 
getMax()215         public int getMax() {
216             return max;
217         }
218     }
219 }
220