001package jmri.jmrit.operations.trains.manualtrainbuilder; 002 003import java.util.*; 004 005import org.jdom2.Element; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009import jmri.InstanceManager; 010import jmri.beans.PropertyChangeSupport; 011import jmri.jmrit.operations.locations.LocationManagerXml; 012import jmri.jmrit.operations.setup.Control; 013import jmri.jmrit.operations.trains.Train; 014import jmri.jmrit.operations.trains.TrainManager; 015 016/** 017 * Train Manual Build. Allows a user to manually assign cars to a train. 018 * 019 * @author Daniel Boudreau Copyright (C) 2026 020 */ 021public class TrainManualBuild extends PropertyChangeSupport implements java.beans.PropertyChangeListener { 022 023 protected String _id = ""; 024 protected String _trainId = ""; 025 protected String _comment = ""; 026 027 // stores manual build items for this manual build 028 protected Hashtable<String, TrainManualBuildItem> _manualBuildHashTable = 029 new Hashtable<String, TrainManualBuildItem>(); 030 protected int _IdNumber = 0; // each item in a manual build gets its own id 031 protected int _sequenceNum = 0; // each item has a unique sequence number 032 033 public static final String LISTCHANGE_CHANGED_PROPERTY = "manualBuildListChange"; // NOI18N 034 public static final String DISPOSE = "manualBuildDispose"; // NOI18N 035 036 public TrainManualBuild(String id, String trainId) { 037 log.debug("New manual build ({}) id: {}", trainId, id); 038 _trainId = trainId; 039 _id = id; 040 } 041 042 public String getId() { 043 return _id; 044 } 045 046 public void setTrainId(String trainId) { 047 String old = _trainId; 048 _trainId = trainId; 049 if (!old.equals(trainId)) { 050 setDirtyAndFirePropertyChange("ManualBuildId", old, trainId); // NOI18N 051 } 052 } 053 054 public String getTrainId() { 055 return _trainId; 056 } 057 058 public String getTrainName() { 059 TrainManager trainManager = InstanceManager.getDefault(TrainManager.class); 060 Train train = trainManager.getTrainById(getTrainId()); 061 if (train != null) { 062 return train.getName(); 063 } 064 return ""; 065 } 066 067 public int getSize() { 068 return _manualBuildHashTable.size(); 069 } 070 071 public void setComment(String comment) { 072 String old = _comment; 073 _comment = comment; 074 if (!old.equals(comment)) { 075 setDirtyAndFirePropertyChange("ManualBuildComment", old, comment); // NOI18N 076 } 077 } 078 079 public String getComment() { 080 return _comment; 081 } 082 083 public void dispose() { 084 setDirtyAndFirePropertyChange(DISPOSE, null, DISPOSE); 085 } 086 087 /** 088 * Adds build item to the end of this manual build 089 * 090 * @return ManualBuildItem created 091 */ 092 public TrainManualBuildItem addItem() { 093 _IdNumber++; 094 _sequenceNum++; 095 String id = _id + "m" + Integer.toString(_IdNumber); 096 log.debug("Adding new item to ({}) id: {}", getTrainId(), id); 097 TrainManualBuildItem mbi = new TrainManualBuildItem(id); 098 mbi.setSequenceId(_sequenceNum); 099 int old = _manualBuildHashTable.size(); 100 _manualBuildHashTable.put(mbi.getId(), mbi); 101 102 setDirtyAndFirePropertyChange(LISTCHANGE_CHANGED_PROPERTY, old, _manualBuildHashTable.size()); 103 // listen for set out and pick up changes to forward 104 mbi.addPropertyChangeListener(this); 105 return mbi; 106 } 107 108 /** 109 * Add a manual build item at a specific place (sequence) in the manual 110 * build. Allowable sequence numbers are 0 to max size of manual build. 0 = 111 * start of list. 112 * 113 * @param sequence Where in the Manual Build to add the item. 114 * @return manual build item 115 */ 116 public TrainManualBuildItem addItem(int sequence) { 117 TrainManualBuildItem mbi = addItem(); 118 if (sequence < 0 || sequence > _manualBuildHashTable.size()) { 119 return mbi; 120 } 121 for (int i = 0; i < _manualBuildHashTable.size() - sequence - 1; i++) { 122 moveItemUp(mbi); 123 } 124 return mbi; 125 } 126 127 /** 128 * Remember a NamedBean Object created outside the manager. 129 * 130 * @param mbi The manual build item to add. 131 */ 132 public void register(TrainManualBuildItem mbi) { 133 int old = _manualBuildHashTable.size(); 134 _manualBuildHashTable.put(mbi.getId(), mbi); 135 136 // find last id created 137 String[] getId = mbi.getId().split("m"); 138 int id = Integer.parseInt(getId[1]); 139 if (id > _IdNumber) { 140 _IdNumber = id; 141 } 142 // find highest sequence number 143 if (mbi.getSequenceId() > _sequenceNum) { 144 _sequenceNum = mbi.getSequenceId(); 145 } 146 setDirtyAndFirePropertyChange(LISTCHANGE_CHANGED_PROPERTY, old, _manualBuildHashTable.size()); 147 // listen for set out and pick up changes to forward 148 mbi.addPropertyChangeListener(this); 149 } 150 151 /** 152 * Delete a manual build item 153 * 154 * @param mbi The manual build item to delete. 155 */ 156 public void deleteItem(TrainManualBuildItem mbi) { 157 if (mbi != null) { 158 mbi.removePropertyChangeListener(this); 159 // subtract from the items's available track length 160 String id = mbi.getId(); 161 mbi.dispose(); 162 int old = _manualBuildHashTable.size(); 163 _manualBuildHashTable.remove(id); 164 resequenceIds(); 165 setDirtyAndFirePropertyChange(LISTCHANGE_CHANGED_PROPERTY, old, _manualBuildHashTable.size()); 166 } 167 } 168 169 /** 170 * Reorder the item sequence numbers for this manual build 171 */ 172 private void resequenceIds() { 173 List<TrainManualBuildItem> manualBuildItems = getItemsBySequenceList(); 174 for (int i = 0; i < manualBuildItems.size(); i++) { 175 // start sequence numbers at 1 176 manualBuildItems.get(i).setSequenceId(i + 1); 177 _sequenceNum = i + 1; 178 } 179 } 180 181 /** 182 * Get a ManualBuildItem by id 183 * 184 * @param id The string id of the ManualBuildItem. 185 * @return manual build item 186 */ 187 public TrainManualBuildItem getItemById(String id) { 188 return _manualBuildHashTable.get(id); 189 } 190 191 private List<TrainManualBuildItem> getItemsByIdList() { 192 String[] arr = new String[_manualBuildHashTable.size()]; 193 List<TrainManualBuildItem> out = new ArrayList<TrainManualBuildItem>(); 194 Enumeration<String> en = _manualBuildHashTable.keys(); 195 int i = 0; 196 while (en.hasMoreElements()) { 197 arr[i++] = en.nextElement(); 198 } 199 Arrays.sort(arr); 200 for (i = 0; i < arr.length; i++) { 201 out.add(getItemById(arr[i])); 202 } 203 return out; 204 } 205 206 /** 207 * Get a list of ManualBuildItems sorted by sequence order 208 * 209 * @return list of ManualBuildItems ordered by sequence 210 */ 211 public List<TrainManualBuildItem> getItemsBySequenceList() { 212 // first get id list 213 List<TrainManualBuildItem> sortList = getItemsByIdList(); 214 // now re-sort 215 List<TrainManualBuildItem> out = new ArrayList<TrainManualBuildItem>(); 216 217 for (TrainManualBuildItem mbi : sortList) { 218 for (int j = 0; j < out.size(); j++) { 219 if (mbi.getSequenceId() < out.get(j).getSequenceId()) { 220 out.add(j, mbi); 221 break; 222 } 223 } 224 if (!out.contains(mbi)) { 225 out.add(mbi); 226 } 227 } 228 return out; 229 } 230 231 /** 232 * Places a ManualBuildItem earlier in the manual build 233 * 234 * @param mbi The ManualBuildItem to move. 235 */ 236 public void moveItemUp(TrainManualBuildItem mbi) { 237 int sequenceId = mbi.getSequenceId(); 238 if (sequenceId - 1 <= 0) { 239 mbi.setSequenceId(_sequenceNum + 1); // move to the end of the list 240 resequenceIds(); 241 } else { 242 // adjust the other item taken by this one 243 TrainManualBuildItem replaceSi = getItemBySequenceId(sequenceId - 1); 244 if (replaceSi != null) { 245 replaceSi.setSequenceId(sequenceId); 246 mbi.setSequenceId(sequenceId - 1); 247 } else { 248 resequenceIds(); // error the sequence number is missing 249 } 250 } 251 setDirtyAndFirePropertyChange(LISTCHANGE_CHANGED_PROPERTY, null, sequenceId); 252 } 253 254 /** 255 * Places a ManualBuildItem later in the manualBuild 256 * 257 * @param mbi The ManualBuildItem to move. 258 */ 259 public void moveItemDown(TrainManualBuildItem mbi) { 260 int sequenceId = mbi.getSequenceId(); 261 if (sequenceId + 1 > _sequenceNum) { 262 mbi.setSequenceId(0); // move to the start of the list 263 resequenceIds(); 264 } else { 265 // adjust the other item taken by this one 266 TrainManualBuildItem replaceSi = getItemBySequenceId(sequenceId + 1); 267 if (replaceSi != null) { 268 replaceSi.setSequenceId(sequenceId); 269 mbi.setSequenceId(sequenceId + 1); 270 } else { 271 resequenceIds(); // error the sequence number is missing 272 } 273 } 274 setDirtyAndFirePropertyChange(LISTCHANGE_CHANGED_PROPERTY, null, sequenceId); 275 } 276 277 public TrainManualBuildItem getItemBySequenceId(int sequenceId) { 278 for (TrainManualBuildItem mbi : getItemsByIdList()) { 279 if (mbi.getSequenceId() == sequenceId) { 280 return mbi; 281 } 282 } 283 return null; 284 } 285 286 /** 287 * Construct this Entry from XML. This member has to remain synchronized 288 * with the detailed DTD in operations-config.xml 289 * 290 * @param e Consist XML element 291 */ 292 public TrainManualBuild(Element e) { 293 org.jdom2.Attribute a; 294 if ((a = e.getAttribute(Xml.ID)) != null) { 295 _id = a.getValue(); 296 } else { 297 log.warn("no id attribute in manualBuild element when reading operations"); 298 } 299 if ((a = e.getAttribute(Xml.TRAIN_ID)) != null) { 300 _trainId = a.getValue(); 301 } 302 if ((a = e.getAttribute(Xml.COMMENT)) != null) { 303 _comment = a.getValue(); 304 } 305 if (e.getChildren(Xml.MANUAL_BUILD_ITEM) != null) { 306 List<Element> eManualBuildItems = e.getChildren(Xml.MANUAL_BUILD_ITEM); 307 log.debug("manualBuild: {} has {} items", getTrainId(), eManualBuildItems.size()); 308 for (Element eManualBuildItem : eManualBuildItems) { 309 register(new TrainManualBuildItem(eManualBuildItem)); 310 } 311 } 312 } 313 314 /** 315 * Create an XML element to represent this Entry. This member has to remain 316 * synchronized with the detailed DTD in operations-config.xml. 317 * 318 * @return Contents in a JDOM Element 319 */ 320 public org.jdom2.Element store() { 321 Element e = new org.jdom2.Element(Xml.MANUAL_BUILD); 322 e.setAttribute(Xml.ID, getId()); 323 e.setAttribute(Xml.TRAIN_ID, getTrainId()); 324 e.setAttribute(Xml.NAME, getTrainName()); 325 e.setAttribute(Xml.COMMENT, getComment()); 326 for (TrainManualBuildItem mbi : getItemsBySequenceList()) { 327 e.addContent(mbi.store()); 328 } 329 330 return e; 331 } 332 333 @Override 334 public void propertyChange(java.beans.PropertyChangeEvent e) { 335 if (Control.SHOW_PROPERTY) { 336 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 337 .getNewValue()); 338 } 339 // forward all manualBuild item changes 340 setDirtyAndFirePropertyChange(e.getPropertyName(), e.getOldValue(), e.getNewValue()); 341 } 342 343 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 344 // set dirty 345 InstanceManager.getDefault(LocationManagerXml.class).setDirty(true); 346 firePropertyChange(p, old, n); 347 } 348 349 private static final Logger log = LoggerFactory.getLogger(TrainManualBuild.class); 350 351}