001package jmri.jmrit.operations.trains.manualtrainbuilder; 002 003import java.beans.PropertyChangeListener; 004import java.util.*; 005 006import org.jdom2.Element; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010import jmri.*; 011import jmri.beans.PropertyChangeSupport; 012import jmri.jmrit.operations.locations.LocationManagerXml; 013import jmri.jmrit.operations.locations.Track; 014import jmri.jmrit.operations.rollingstock.cars.CarRoads; 015import jmri.jmrit.operations.rollingstock.cars.CarTypes; 016import jmri.jmrit.operations.setup.Control; 017 018/** 019 * Manages train manual builds 020 * 021 * @author Daniel Boudreau Copyright (C) 2026 022 */ 023public class TrainManualBuildManager extends PropertyChangeSupport implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize, PropertyChangeListener { 024 025 public static final String LISTLENGTH_CHANGED_PROPERTY = "manualBuildListLength"; // NOI18N 026 027 public TrainManualBuildManager() { 028 } 029 030 private int _id = 0; 031 032 public void dispose() { 033 _manualBuildHashTable.clear(); 034 } 035 036 // stores known ManualBuild instances by id 037 protected Hashtable<String, TrainManualBuild> _manualBuildHashTable = new Hashtable<String, TrainManualBuild>(); 038 039 /** 040 * @return Number of manual builds 041 */ 042 public int numEntries() { 043 return _manualBuildHashTable.size(); 044 } 045 046 /** 047 * @param trainId The train id for the manual build 048 * @return requested ManualBuild object or null if none exists 049 */ 050 public TrainManualBuild getManualBuildByTrainId(String trainId) { 051 TrainManualBuild mb; 052 Enumeration<TrainManualBuild> en = _manualBuildHashTable.elements(); 053 while (en.hasMoreElements()) { 054 mb = en.nextElement(); 055 if (mb.getTrainId().equals(trainId)) { 056 return mb; 057 } 058 } 059 return null; 060 } 061 062 public TrainManualBuild getManualBuildById(String id) { 063 return _manualBuildHashTable.get(id); 064 } 065 066 /** 067 * Finds an existing manual build or creates a new manual build if needed. 068 * 069 * @param trainId The train id for this manual build 070 * 071 * 072 * @return new manual build or existing manual build 073 */ 074 public TrainManualBuild newManualBuild(String trainId) { 075 TrainManualBuild mb = getManualBuildByTrainId(trainId); 076 if (mb == null && !trainId.isBlank()) { 077 _id++; 078 mb = new TrainManualBuild(Integer.toString(_id), trainId); 079 int oldSize = _manualBuildHashTable.size(); 080 _manualBuildHashTable.put(mb.getId(), mb); 081 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _manualBuildHashTable.size()); 082 } 083 return mb; 084 } 085 086 /** 087 * Remember a NamedBean Object created outside the manager. 088 * 089 * @param manualBuild The ManualBuild to add. 090 */ 091 public void register(TrainManualBuild manualBuild) { 092 int oldSize = _manualBuildHashTable.size(); 093 _manualBuildHashTable.put(manualBuild.getId(), manualBuild); 094 // find last id created 095 int id = Integer.parseInt(manualBuild.getId()); 096 if (id > _id) { 097 _id = id; 098 } 099 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _manualBuildHashTable.size()); 100 } 101 102 /** 103 * Forget a NamedBean Object created outside the manager. 104 * 105 * @param manualBuild The ManualBuild to delete. 106 */ 107 public void deregister(TrainManualBuild manualBuild) { 108 if (manualBuild == null) { 109 return; 110 } 111 manualBuild.dispose(); 112 int oldSize = _manualBuildHashTable.size(); 113 _manualBuildHashTable.remove(manualBuild.getId()); 114 setDirtyAndFirePropertyChange(LISTLENGTH_CHANGED_PROPERTY, oldSize, _manualBuildHashTable.size()); 115 } 116 117 /** 118 * Sort by manual build train names 119 * 120 * @return list of manual builds ordered by train names 121 */ 122 public List<TrainManualBuild> getManualBuildsByTrainNameList() { 123 List<TrainManualBuild> sortList = getList(); 124 // now re-sort 125 List<TrainManualBuild> out = new ArrayList<TrainManualBuild>(); 126 for (TrainManualBuild mb : sortList) { 127 for (int j = 0; j < out.size(); j++) { 128 if (mb.getTrainName().compareToIgnoreCase(out.get(j).getTrainName()) < 0) { 129 out.add(j, mb); 130 break; 131 } 132 } 133 if (!out.contains(mb)) { 134 out.add(mb); 135 } 136 } 137 return out; 138 139 } 140 141 /** 142 * Sort by manual build id number 143 * 144 * @return list of manual builds ordered by id number 145 */ 146 public List<TrainManualBuild> getManualBuildsByIdList() { 147 List<TrainManualBuild> sortList = getList(); 148 // now re-sort 149 List<TrainManualBuild> out = new ArrayList<TrainManualBuild>(); 150 for (TrainManualBuild mb : sortList) { 151 for (int j = 0; j < out.size(); j++) { 152 try { 153 if (Integer.parseInt(mb.getId()) < Integer.parseInt(out.get(j).getId())) { 154 out.add(j, mb); 155 break; 156 } 157 } catch (NumberFormatException e) { 158 log.debug("list id number isn't a number"); 159 } 160 } 161 if (!out.contains(mb)) { 162 out.add(mb); 163 } 164 } 165 return out; 166 } 167 168 private List<TrainManualBuild> getList() { 169 List<TrainManualBuild> out = new ArrayList<TrainManualBuild>(); 170 Enumeration<TrainManualBuild> en = _manualBuildHashTable.elements(); 171 while (en.hasMoreElements()) { 172 out.add(en.nextElement()); 173 } 174 return out; 175 } 176 177 public TrainManualBuild copyManualBuild(TrainManualBuild manualBuild, String newManualBuildName) { 178 TrainManualBuild newManualBuild = newManualBuild(newManualBuildName); 179 for (TrainManualBuildItem mbi : manualBuild.getItemsBySequenceList()) { 180 TrainManualBuildItem newMbi = newManualBuild.addItem(); 181 newMbi.copyManualBuildItem(mbi); 182 } 183 return newManualBuild; 184 } 185 186 187 /** 188 * Replaces car type in all manual builds. 189 * 190 * @param oldType car type to be replaced. 191 * @param newType replacement car type. 192 */ 193 public void replaceType(String oldType, String newType) { 194 for (TrainManualBuild sch : getList()) { 195 for (TrainManualBuildItem si : sch.getItemsBySequenceList()) { 196 if (si.getTypeName().equals(oldType)) { 197 si.setTypeName(newType); 198 } 199 } 200 } 201 } 202 203 /** 204 * Replaces car roads in all manual builds. 205 * 206 * @param oldRoad car road to be replaced. 207 * @param newRoad replacement car road. 208 */ 209 public void replaceRoad(String oldRoad, String newRoad) { 210 if (newRoad == null) { 211 return; 212 } 213 for (TrainManualBuild mb : getList()) { 214 for (TrainManualBuildItem mbi : mb.getItemsBySequenceList()) { 215 if (mbi.getRoadName().equals(oldRoad)) { 216 mbi.setRoadName(newRoad); 217 } 218 } 219 } 220 } 221 222 /** 223 * Replaces car loads in all manual builds with specific car type. 224 * 225 * @param type car type. 226 * @param oldLoad car load to be replaced. 227 * @param newLoad replacement car load. 228 */ 229 public void replaceLoad(String type, String oldLoad, String newLoad) { 230 for (TrainManualBuild mb : getList()) { 231 for (TrainManualBuildItem mbi : mb.getItemsBySequenceList()) { 232 if (mbi.getTypeName().equals(type) && mbi.getLoadName().equals(oldLoad)) { 233 if (newLoad != null) { 234 mbi.setLoadName(newLoad); 235 } else { 236 mbi.setLoadName(TrainManualBuildItem.NONE); 237 } 238 } 239 } 240 } 241 } 242 243 public void replaceTrack(Track oldTrack, Track newTrack) { 244 for (TrainManualBuild mb : getList()) { 245 for (TrainManualBuildItem mbi : mb.getItemsBySequenceList()) { 246 if (mbi.getDestinationTrack() == oldTrack) { 247 mbi.setDestination(newTrack.getLocation()); 248 mbi.setDestinationTrack(newTrack); 249 } 250 } 251 } 252 } 253 254 public void load(Element root) { 255 if (root.getChild(Xml.MANUAL_BUILDS) != null) { 256 List<Element> eManualBuilds = root.getChild(Xml.MANUAL_BUILDS).getChildren(Xml.MANUAL_BUILD); 257 log.debug("readFile sees {} manual builds", eManualBuilds.size()); 258 for (Element eManualBuild : eManualBuilds) { 259 register(new TrainManualBuild(eManualBuild)); 260 } 261 } 262 } 263 264 public void store(Element root) { 265 Element values; 266 root.addContent(values = new Element(Xml.MANUAL_BUILDS)); 267 // add entries 268 for (TrainManualBuild manualBuild : getManualBuildsByIdList()) { 269 values.addContent(manualBuild.store()); 270 } 271 } 272 273 /** 274 * Check for car type and road name changes. 275 */ 276 @Override 277 public void propertyChange(java.beans.PropertyChangeEvent e) { 278 if (Control.SHOW_PROPERTY) { 279 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 280 .getNewValue()); 281 } 282 if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY)) { 283 replaceType((String) e.getOldValue(), (String) e.getNewValue()); 284 } 285 if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) { 286 replaceRoad((String) e.getOldValue(), (String) e.getNewValue()); 287 } 288 } 289 290 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 291 // set dirty 292 InstanceManager.getDefault(LocationManagerXml.class).setDirty(true); 293 firePropertyChange(p, old, n); 294 } 295 296 private static final Logger log = LoggerFactory.getLogger(TrainManualBuildManager.class); 297 298 @Override 299 public void initialize() { 300 InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this); 301 InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this); 302 } 303 304}