Skip to content

Commit 5e511b4

Browse files
authored
Fix issue 12826 (UnitSeparator#categorize:213 - java.util.ConcurrentModificationException) (#12828)
* Fix issue 12826 (UnitSeparator#categorize:213 - java.util.ConcurrentModificationException) PlacePanel.java - new method updateUnitsInUnitsToPlacePanel that ensures copying the unit collection (existed already in method updateStep, but not in gameDataChanged or updateUnits) - Cleanup: extract new methods from declaration of variable placeMapSelectionListener which are getUnitsToPlace, getScrollPaneFromChooser, getPreferredHeight and getPreferredWidth * PlayerUnitsPanel ToDo done via redraw Replace invalidate(); validate(); revalidate();getParent().invalidate(); with SwingComponents.redraw(this);
1 parent 8b4bbda commit 5e511b4

File tree

2 files changed

+109
-98
lines changed

2 files changed

+109
-98
lines changed

game-app/game-core/src/main/java/games/strategy/triplea/odds/calculator/PlayerUnitsPanel.java

+2-5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import lombok.Getter;
2525
import org.triplea.java.collections.CollectionUtils;
2626
import org.triplea.java.collections.IntegerMap;
27+
import org.triplea.swing.SwingComponents;
2728

2829
/** Panel showing full list of units for a player in a given battle simulation. */
2930
public class PlayerUnitsPanel extends JPanel {
@@ -102,11 +103,7 @@ public void init(
102103
}
103104
}
104105

105-
// TODO: probably do not need to do this much revalidation.
106-
invalidate();
107-
validate();
108-
revalidate();
109-
getParent().invalidate();
106+
SwingComponents.redraw(this);
110107
}
111108

112109
/**

game-app/game-headed/src/main/java/games/strategy/triplea/ui/PlacePanel.java

+107-93
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.ArrayList;
2626
import java.util.Arrays;
2727
import java.util.Collection;
28+
import java.util.Collections;
2829
import java.util.List;
2930
import java.util.Map;
3031
import java.util.function.Predicate;
@@ -34,21 +35,71 @@
3435
import javax.swing.JPanel;
3536
import javax.swing.JScrollPane;
3637
import javax.swing.SwingUtilities;
38+
import org.jetbrains.annotations.NotNull;
3739
import org.triplea.java.collections.CollectionUtils;
3840
import org.triplea.swing.SwingComponents;
3941

4042
class PlacePanel extends AbstractMovePanel implements GameDataChangeListener {
4143
private static final long serialVersionUID = -4411301492537704785L;
4244
private final JLabel leftToPlaceLabel = createIndentedLabel();
43-
private PlaceData placeData;
45+
private transient PlaceData placeData;
4446

4547
private final SimpleUnitPanel unitsToPlacePanel;
4648

4749
private GamePlayer lastPlayer;
4850
private boolean postProductionStep;
4951

50-
private final MapSelectionListener placeMapSelectionListener =
52+
private final transient MapSelectionListener placeMapSelectionListener =
5153
new DefaultMapSelectionListener() {
54+
55+
private PlaceableUnits getUnitsToPlace(final Territory territory) {
56+
try (GameData.Unlocker ignored = getData().acquireReadLock()) {
57+
// not our territory
58+
if (!territory.isWater() && !territory.isOwnedBy(getCurrentPlayer())) {
59+
if (GameStepPropertiesHelper.isBid(getData())) {
60+
final PlayerAttachment pa = PlayerAttachment.get(territory.getOwner());
61+
if ((pa == null || !pa.getGiveUnitControl().contains(getCurrentPlayer()))
62+
&& !territory.anyUnitsMatch(Matches.unitIsOwnedBy(getCurrentPlayer()))) {
63+
return new PlaceableUnits();
64+
}
65+
} else {
66+
return new PlaceableUnits();
67+
}
68+
}
69+
// get the units that can be placed on this territory.
70+
Collection<Unit> units = getCurrentPlayer().getUnits();
71+
if (territory.isWater()) {
72+
GameProperties properties = getData().getProperties();
73+
if (!(Properties.getProduceFightersOnCarriers(properties)
74+
|| Properties.getProduceNewFightersOnOldCarriers(properties)
75+
|| Properties.getLhtrCarrierProductionRules(properties)
76+
|| GameStepPropertiesHelper.isBid(getData()))) {
77+
units = CollectionUtils.getMatches(units, Matches.unitIsSea());
78+
} else {
79+
final Predicate<Unit> unitIsSeaOrCanLandOnCarrier =
80+
Matches.unitIsSea().or(Matches.unitCanLandOnCarrier());
81+
units = CollectionUtils.getMatches(units, unitIsSeaOrCanLandOnCarrier);
82+
}
83+
} else {
84+
units = CollectionUtils.getMatches(units, Matches.unitIsNotSea());
85+
}
86+
if (units.isEmpty()) {
87+
return new PlaceableUnits();
88+
}
89+
final IAbstractPlaceDelegate placeDel =
90+
(IAbstractPlaceDelegate) getPlayerBridge().getRemoteDelegate();
91+
final PlaceableUnits production = placeDel.getPlaceableUnits(units, territory);
92+
if (production.isError()) {
93+
JOptionPane.showMessageDialog(
94+
getTopLevelAncestor(),
95+
production.getErrorMessage() + "\n\n",
96+
"Cannot produce units",
97+
JOptionPane.INFORMATION_MESSAGE);
98+
}
99+
return production;
100+
}
101+
}
102+
52103
@Override
53104
public void territorySelected(final Territory territory, final MouseDetails e) {
54105
if (!isActive() || (e.getButton() != MouseEvent.BUTTON1)) {
@@ -66,21 +117,7 @@ public void territorySelected(final Territory territory, final MouseDetails e) {
66117
if (maxUnits >= 0) {
67118
chooser.setMaxAndShowMaxButton(maxUnits);
68119
}
69-
final Dimension screenResolution = Toolkit.getDefaultToolkit().getScreenSize();
70-
final int availHeight = screenResolution.height - 120;
71-
final int availWidth = screenResolution.width - 40;
72-
final JScrollPane scroll = new JScrollPane(chooser);
73-
scroll.setBorder(BorderFactory.createEmptyBorder());
74-
scroll.setPreferredSize(
75-
new Dimension(
76-
(scroll.getPreferredSize().width > availWidth
77-
? availWidth
78-
: (scroll.getPreferredSize().width
79-
+ (scroll.getPreferredSize().height > availHeight ? 20 : 0))),
80-
(scroll.getPreferredSize().height > availHeight
81-
? availHeight
82-
: (scroll.getPreferredSize().height
83-
+ (scroll.getPreferredSize().width > availWidth ? 26 : 0)))));
120+
final JScrollPane scroll = getScrollPaneFromChooser(chooser);
84121
final int option =
85122
JOptionPane.showOptionDialog(
86123
getTopLevelAncestor(),
@@ -92,15 +129,43 @@ public void territorySelected(final Territory territory, final MouseDetails e) {
92129
null,
93130
null);
94131
if (option == JOptionPane.OK_OPTION) {
95-
final Collection<Unit> choosen = chooser.getSelected();
96-
placeData = new PlaceData(choosen, territory);
132+
final Collection<Unit> selectedUnits = chooser.getSelected();
133+
placeData = new PlaceData(selectedUnits, territory);
97134
updateUnits();
98-
if (choosen.containsAll(units)) {
135+
if (selectedUnits.containsAll(units)) {
99136
leftToPlaceLabel.setText("");
100137
}
101138
release();
102139
}
103140
}
141+
142+
@NotNull
143+
private JScrollPane getScrollPaneFromChooser(final UnitChooser chooser) {
144+
final Dimension screenResolution = Toolkit.getDefaultToolkit().getScreenSize();
145+
final int availHeight = screenResolution.height - 120;
146+
final int availWidth = screenResolution.width - 40;
147+
final JScrollPane scroll = new JScrollPane(chooser);
148+
scroll.setBorder(BorderFactory.createEmptyBorder());
149+
scroll.setPreferredSize(
150+
new Dimension(
151+
(scroll.getPreferredSize().width > availWidth
152+
? availWidth
153+
: getPreferredWith(scroll, availHeight)),
154+
(scroll.getPreferredSize().height > availHeight
155+
? availHeight
156+
: getPreferredHeight(scroll, availWidth))));
157+
return scroll;
158+
}
159+
160+
private int getPreferredHeight(final JScrollPane scroll, final int availWidth) {
161+
return scroll.getPreferredSize().height
162+
+ (scroll.getPreferredSize().width > availWidth ? 26 : 0);
163+
}
164+
165+
private int getPreferredWith(final JScrollPane scroll, final int availHeight) {
166+
return scroll.getPreferredSize().width
167+
+ (scroll.getPreferredSize().height > availHeight ? 20 : 0);
168+
}
104169
};
105170

106171
PlacePanel(final GameData data, final MapPanel map, final TripleAFrame frame) {
@@ -147,42 +212,39 @@ private void updateStep() {
147212
}
148213

149214
if (showUnitsToPlace) {
150-
// Small hack: copy the unit list before passing it to a new thread.
151-
// This is to prevent ConcurrentModification. If the 'unitsToPlace' list is modified
152-
// later in this thread, before "SwingUtilities.invokeLater" can execute and complete,
153-
// then we will get a ConcurrentModification exception.
154-
// Ideally we would not modify the 'unitsToPlace' collection again except when
155-
// the swing thread signals that the user has taken action.. Short of that, we create a copy
156-
// here.
157-
final Collection<Unit> unitsToPlaceCopy = new ArrayList<>(unitsToPlace);
158-
SwingUtilities.invokeLater(
159-
() -> {
160-
unitsToPlacePanel.setUnits(unitsToPlaceCopy);
161-
SwingComponents.redraw(unitsToPlacePanel);
162-
});
215+
updateUnitsInUnitsToPlacePanel(unitsToPlace);
163216
} else {
164217
SwingUtilities.invokeLater(unitsToPlacePanel::removeAll);
165218
}
166219
}
167220

221+
private void updateUnitsInUnitsToPlacePanel(final Collection<Unit> newUnitsToPlace) {
222+
// Small hack: copy the unit list before passing it to a new thread.
223+
// This is to prevent ConcurrentModification. If the 'unitsToPlace' list is modified
224+
// later in this thread, before "SwingUtilities.invokeLater" can execute and complete,
225+
// then we will get a ConcurrentModification exception.
226+
// Ideally we would not modify the 'unitsToPlace' collection again except when
227+
// the swing thread signals that the user has taken action.
228+
// Short of that, we create a copy here.
229+
final Collection<Unit> unitsToPlaceCopy =
230+
Collections.unmodifiableCollection(new ArrayList<>(newUnitsToPlace));
231+
SwingUtilities.invokeLater(
232+
() -> {
233+
unitsToPlacePanel.setUnits(unitsToPlaceCopy);
234+
SwingComponents.redraw(unitsToPlacePanel);
235+
});
236+
}
237+
168238
@Override
169239
public void gameDataChanged(final Change change) {
170-
final Collection<Unit> unitsToPlace;
171240
final GameData data = getData();
172241
try (GameData.Unlocker ignored = data.acquireReadLock()) {
173242
final GamePlayer player = data.getSequence().getStep().getPlayerId();
174-
if (player == null) {
175-
return;
243+
if (player != null) {
244+
final Collection<Unit> unitsToPlace = player.getUnits();
245+
updateUnitsInUnitsToPlacePanel(unitsToPlace);
176246
}
177-
unitsToPlace = player.getUnits();
178247
}
179-
180-
SwingUtilities.invokeLater(
181-
() -> {
182-
unitsToPlacePanel.setUnits(unitsToPlace);
183-
unitsToPlacePanel.revalidate();
184-
unitsToPlacePanel.repaint();
185-
});
186248
}
187249

188250
@Override
@@ -210,54 +272,6 @@ PlaceData waitForPlace(final boolean bid, final PlayerBridge playerBridge) {
210272
return placeData;
211273
}
212274

213-
private PlaceableUnits getUnitsToPlace(final Territory territory) {
214-
try (GameData.Unlocker ignored = getData().acquireReadLock()) {
215-
// not our territory
216-
if (!territory.isWater() && !territory.isOwnedBy(getCurrentPlayer())) {
217-
if (GameStepPropertiesHelper.isBid(getData())) {
218-
final PlayerAttachment pa = PlayerAttachment.get(territory.getOwner());
219-
if ((pa == null || !pa.getGiveUnitControl().contains(getCurrentPlayer()))
220-
&& !territory.anyUnitsMatch(Matches.unitIsOwnedBy(getCurrentPlayer()))) {
221-
return new PlaceableUnits();
222-
}
223-
} else {
224-
return new PlaceableUnits();
225-
}
226-
}
227-
// get the units that can be placed on this territory.
228-
Collection<Unit> units = getCurrentPlayer().getUnits();
229-
if (territory.isWater()) {
230-
GameProperties properties = getData().getProperties();
231-
if (!(Properties.getProduceFightersOnCarriers(properties)
232-
|| Properties.getProduceNewFightersOnOldCarriers(properties)
233-
|| Properties.getLhtrCarrierProductionRules(properties)
234-
|| GameStepPropertiesHelper.isBid(getData()))) {
235-
units = CollectionUtils.getMatches(units, Matches.unitIsSea());
236-
} else {
237-
final Predicate<Unit> unitIsSeaOrCanLandOnCarrier =
238-
Matches.unitIsSea().or(Matches.unitCanLandOnCarrier());
239-
units = CollectionUtils.getMatches(units, unitIsSeaOrCanLandOnCarrier);
240-
}
241-
} else {
242-
units = CollectionUtils.getMatches(units, Matches.unitIsNotSea());
243-
}
244-
if (units.isEmpty()) {
245-
return new PlaceableUnits();
246-
}
247-
final IAbstractPlaceDelegate placeDel =
248-
(IAbstractPlaceDelegate) getPlayerBridge().getRemoteDelegate();
249-
final PlaceableUnits production = placeDel.getPlaceableUnits(units, territory);
250-
if (production.isError()) {
251-
JOptionPane.showMessageDialog(
252-
getTopLevelAncestor(),
253-
production.getErrorMessage() + "\n\n",
254-
"Cannot produce units",
255-
JOptionPane.INFORMATION_MESSAGE);
256-
}
257-
return production;
258-
}
259-
}
260-
261275
@Override
262276
public String toString() {
263277
return "PlacePanel";
@@ -315,6 +329,6 @@ protected final List<Component> getAdditionalButtons() {
315329
}
316330

317331
private void updateUnits() {
318-
unitsToPlacePanel.setUnits(getCurrentPlayer().getUnits());
332+
updateUnitsInUnitsToPlacePanel(getCurrentPlayer().getUnits());
319333
}
320334
}

0 commit comments

Comments
 (0)