001/** 002 * TREETOPSDataReader -- Reads data from NASA TREETOPS/CLVTOPS simulation data files. 003 * 004 * Copyright (C) 2017-2024, by Joseph A. Huwaldt. All rights reserved. 005 * 006 * This library is free software; you can redistribute it and/or modify it under the terms 007 * of the GNU Lesser General Public License as published by the Free Software Foundation; 008 * either version 2 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public License along with 015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 017 */ 018package jahuwaldt.js.datareader; 019 020import jahuwaldt.io.FileUtils; 021import jahuwaldt.js.unit.EditUnitSetDialog; 022import jahuwaldt.js.unit.UnitSet; 023import jahuwaldt.swing.AppUtilities; 024import java.awt.Frame; 025import java.io.*; 026import java.lang.reflect.InvocationTargetException; 027import java.lang.reflect.Method; 028import java.text.MessageFormat; 029import java.util.*; 030import javax.measure.quantity.AngularVelocity; 031import javax.measure.quantity.Dimensionless; 032import javax.measure.unit.SI; 033import javax.measure.unit.Unit; 034 035/** 036 * This class reads data from a NASA TREETOPS/CLVTOPS *.dat and matching *.crf file and 037 * returns a list of DataSet data structures containing the simulation data. 038 * 039 * <p> Modified by: Joseph A. Huwaldt </p> 040 * 041 * @author Joseph A. Huwaldt, Date: March 17, 2017 042 * @version March 27, 2024 043 */ 044public class TREETOPSDataReader implements DataReader { 045 046 // A brief description of the data read by this reader. 047 private static final String DESCRIPTION = RESOURCES.getString("treetopsDesc"); 048 049 // The preferred file extension for files of this reader's type. 050 public static final String EXTENSION = RESOURCES.getString("treetopsDefExt"); 051 052 // Define constants for each element type. 053 private static final String BODY = "Body"; 054 private static final String HINGE = "Hinge"; 055 private static final String SENSOR = "Sensor"; 056 private static final String FUNCTION = "Function"; 057 private static final String ACTUATOR = "Actuator"; 058 private static final String DEVICE = "Device"; 059 private static final String CONTROLLER_OUT = "Controller Output"; 060 private static final String CONTROLLER_IN = "Controller Input"; 061 062 // Define dictionaries containing the output variable names, descriptions and units. 063 private static final HashMap<String,String[]> OUT_VARS = new HashMap(); 064 private static final HashMap<String,String[]> OUT_DESC = new HashMap(); 065 private static final HashMap<String,String[]> OUT_UNITS = new HashMap(); 066 067 // Define a dictionary mapping the element type to it's abbreviation. 068 private static final HashMap<String,String> IDABR = new HashMap(); 069 070 static { 071 // Define the body output variable names, descriptions and units. 072 String[] bodyOutputVars = RESOURCES.getString("ttBodyOutputVars").split(","); 073 String[] bodyOutputDesc = RESOURCES.getString("ttBodyOutputDesc").split(","); 074 String[] bodyOutputUnits = RESOURCES.getString("ttBodyOutputUnits").split(","); 075 076 // Store in the appropriate dictionaries. 077 OUT_VARS.put(BODY, bodyOutputVars); 078 OUT_DESC.put(BODY, bodyOutputDesc); 079 OUT_UNITS.put(BODY, bodyOutputUnits); 080 081 // Define the hinge output variable names, descriptions and units. 082 String[] hingeOutputVars = RESOURCES.getString("ttHingeOutputVars").split(","); 083 String[] hingeOutputDesc = RESOURCES.getString("ttHingeOutputDesc").split(","); 084 String[] hingeOutputUnits = RESOURCES.getString("ttHingeOutputUnits").split(","); 085 086 // Store in the appropriate dictionaries. 087 OUT_VARS.put(HINGE, hingeOutputVars); 088 OUT_DESC.put(HINGE, hingeOutputDesc); 089 OUT_UNITS.put(HINGE, hingeOutputUnits); 090 091 // Define the element type ID abbreviations. 092 IDABR.put(BODY, "BO"); 093 IDABR.put(HINGE, "HI"); 094 IDABR.put(SENSOR, "SE"); 095 IDABR.put(FUNCTION, "FU"); 096 IDABR.put(ACTUATOR, "AC"); 097 IDABR.put(DEVICE, "DEMAG"); 098 IDABR.put(CONTROLLER_OUT, "RC"); 099 IDABR.put(CONTROLLER_IN, "UC"); 100 } 101 102 // Since the TREETOPS data is stored in a space delimited array, we can just use 103 // The tab data reader to process the *.dat file. 104 private final DataReader datReader = new TabDataReader(); 105 106 /** 107 * Returns a string representation of the object. This will return a brief description 108 * of the format read by this reader. 109 */ 110 @Override 111 public String toString() { 112 return DESCRIPTION; 113 } 114 115 /** 116 * Returns the preferred file extension (not including the ".") for files of this 117 * DataReader's type. 118 * 119 * @return The preferred file extension for this file's type. 120 */ 121 @Override 122 public String getExtension() { 123 return EXTENSION; 124 } 125 126 /** 127 * Compares this object with the specified object for order based on the 128 * <code>toString().compareTo(o.toString())</code> method. Returns a negative integer, 129 * zero, or a positive integer as this object is less than, equal to, or greater than 130 * the specified object. 131 */ 132 @Override 133 public int compareTo(DataReader o) { 134 return this.toString().compareTo(o.toString()); 135 } 136 137 /** 138 * Method that determines if this reader can read data from the specified input 139 * stream. 140 * 141 * @param pathName The path to the file to be read. 142 * @param input An input stream containing the data to be read. Any methods that 143 * read from this stream must first set a mark and then reset back to 144 * that mark before the method returns (even if it returns with an 145 * exception). 146 * @return DataReader.NO if the file is not recognized at all or DataReader.MAYBE if 147 * the file has an appropriate extension. 148 * @throws java.io.IOException If the input stream could not be read from. 149 */ 150 @Override 151 public int canReadData(String pathName, BufferedInputStream input) throws IOException { 152 int answer = DataReader.NO; 153 154 pathName = pathName.trim(); 155 String prefix = FileUtils.getFileNameWithoutExtension(pathName); 156 pathName = pathName.toLowerCase(); 157 158 File crfFile = new File(prefix + ".crf"); 159 if (crfFile.exists()) { 160 if (pathName.endsWith(".dat")) { 161 answer = DataReader.YES; 162 } 163 } 164 165 return answer; 166 } 167 168 /** 169 * Returns false. This class can not write data to a CLVTOPS formatted file stream. 170 * 171 * @return Always returns false. 172 */ 173 @Override 174 public boolean canWriteData() { 175 return false; 176 } 177 178 /** 179 * Method that reads in CLVTOPS formatted simulation data from the specified 180 * input stream and returns that data as a list of {@link DataSet} objects. 181 * 182 * @param pathName The path to the file to be read. In addition to the *.dat file that 183 * this should point to, there must be a *.crf file with the same prefix in the same 184 * directory that will also be read in. 185 * @param input An input stream containing the space delimited array data file (*.dat). 186 * @return A list of DataSet objects that contains the data read in from the 187 * specified stream (will contain a single data set which will contain a single case 188 * with ArrayParam objects for each column). 189 * @throws IOException If there is a problem reading the specified stream. 190 */ 191 @Override 192 public List<DataSet> read(String pathName, InputStream input) throws IOException { 193 194 // Define the units for the data. 195 UnitSet nativeUnits = askForUnitSet(); 196 if (nativeUnits == null) 197 return null; 198 199 List<DataSet> data = read(pathName, input, nativeUnits); 200 201 return data; 202 } 203 204 /** 205 * Method that reads in CLVTOPS formatted simulation data from the specified 206 * input stream and returns that data as a list of {@link DataSet} objects. 207 * 208 * @param pathName The path to the file to be read. In addition to the *.dat file that 209 * this should point to, there must be a *.crf file with the same prefix in the same 210 * directory that will also be read in. 211 * @param input An input stream containing the space delimited array data file (*.dat). 212 * @param nativeUnits The units for the data in the *.dat file. 213 * @return A list of DataSet objects that contains the data read in from the 214 * specified stream (will contain a single data set which will contain a single case 215 * with ArrayParam objects for each column). 216 * @throws IOException If there is a problem reading the specified stream. 217 */ 218 public List<DataSet> read(String pathName, InputStream input, UnitSet nativeUnits) throws IOException { 219 Objects.requireNonNull(nativeUnits); 220 221 // Start by reading in the tabular data from the input stream pointing to the 222 // *.dat file. 223 List<DataSet> data = datReader.read(pathName, input); 224 225 // Now parse the CRF file to define the names for each column in the table. 226 File crfFile = new File(FileUtils.getFileNameWithoutExtension(pathName) + ".crf"); 227 read(crfFile, data.get(0).get(0), nativeUnits); 228 229 return data; 230 } 231 232 /** 233 * Method that asks the user to select the units for the data being read in from the file. 234 * 235 * @return The unit set for the data in the file. 236 */ 237 private static UnitSet askForUnitSet() { 238 239 // Define the default unit set for the locale. 240 UnitSet nativeUnits = new UnitSet(UnitSet.UnitSystem.SI_MKS); 241 if (Locale.getDefault().equals(Locale.US)) 242 nativeUnits = new UnitSet(UnitSet.UnitSystem.US_FSS); 243 244 // Ask the user to select the units for the data in the file. 245 EditUnitSetDialog dialog = new EditUnitSetDialog(null, RESOURCES.getString("editUnitSetTitle"), 246 RESOURCES.getString("editUnitSetMsg"), nativeUnits); 247 dialog.setLocation(AppUtilities.dialogPosition(dialog)); 248 dialog.setVisible(true); 249 nativeUnits = dialog.getUnits(); 250 dialog.dispose(); 251 252 return nativeUnits; 253 } 254 255 /** 256 * Read in the CLVTOPS CRF file and parse its contents. The CRF file defines what 257 * each column in the output data file is. 258 * 259 * @param crf A reference to the *.crf file for the data file (*.dat) being read in. 260 * @param rawData The raw data read in from the *.dat file. 261 * @param nativeUnits The units for the data in the *.dat file. 262 * @return A reference to the input rawData after the names of each array have been 263 * changed to reflect the data in the CRF file. 264 * @throws IOException 265 */ 266 private static DataCase read(File crf, DataCase rawData, UnitSet nativeUnits) throws IOException { 267 // Open the CRF file and get a line number reader to it. 268 try (FileInputStream is = new FileInputStream(crf)) { 269 LineNumberReader reader = new LineNumberReader(new InputStreamReader(is)); 270 271 // Parse the meta-data from the CRF file. 272 parseCRFStream(reader, rawData, nativeUnits); 273 } 274 275 return rawData; 276 } 277 278 /** 279 * Method that actually parses the CRF file. 280 * 281 * @param in A reader that reads in the characters from the CRF file. 282 * @param data A list of all the ArrayParams as read from the tabular data file 283 * (one ArrayParam per column in the CLVTOPS array). These 284 * parameters will be modified by this method by adding names, 285 * units, and reference data. 286 * @param nativeUnits The system of units used by the data. 287 */ 288 private static void parseCRFStream(LineNumberReader in, DataCase data, UnitSet nativeUnits) throws IOException { 289 290 try { 291 // Skip down to the start of the array data. 292 String aLine = in.readLine(); 293 while ((aLine != null) && (!aLine.startsWith(" 1 TIME"))) { 294 aLine = in.readLine(); 295 } 296 if (aLine == null) 297 throw new IOException("Could not find the 1st data line in CRF file."); 298 299 // Time is always the 1st parameter. 300 ArrayParam arr = (ArrayParam)data.get(0); 301 arr.setName("TIME"); 302 arr.setUserObject(RESOURCES.getString("ttSimTimeDesc")); 303 arr = arr.changeTo(nativeUnits.time()); 304 data.set(0, arr); 305 306 // Define a set of dictionaries for each type of data that could be in the DAT file. 307 HashMap<String,List<String>> sensorData = new HashMap(); 308 HashMap<String,List<String>> functionData = new HashMap(); 309 HashMap<String,List<String>> actuatorData = new HashMap(); 310 HashMap<String,List<String>> deviceData = new HashMap(); 311 HashMap<String,List<String>> controllerOutData = new HashMap(); 312 HashMap<String,List<String>> controllerInData = new HashMap(); 313 HashMap<String,List<String>> thetaData = new HashMap(); 314 HashMap<String,List<String>> thDotData = new HashMap(); 315 HashMap<String,List<String>> htransData = new HashMap(); 316 HashMap<String,List<String>> hvelData = new HashMap(); 317 HashMap<String,List<String>> hcontFData = new HashMap(); 318 HashMap<String,List<String>> hcontMData = new HashMap(); 319 HashMap<String,List<String>> hconstFData = new HashMap(); 320 HashMap<String,List<String>> hconstMData = new HashMap(); 321 HashMap<String,List<String>> wjiData = new HashMap(); 322 HashMap<String,List<String>> qbodyData = new HashMap(); 323 HashMap<String,List<String>> rvectData = new HashMap(); 324 HashMap<String,List<String>> massData = new HashMap(); 325 HashMap<String,List<String>> cmData = new HashMap(); 326 HashMap<String,List<String>> moipoiData = new HashMap(); 327 HashMap<String,List<String>> etaData = new HashMap(); 328 HashMap<String,List<String>> etaDotData = new HashMap(); 329 HashMap<String,List<String>> udjbjData = new HashMap(); 330 HashMap<String,List<String>> udotjData = new HashMap(); 331 HashMap<String,List<String>> psiEtaData = new HashMap(); 332 HashMap<String,List<String>> psiEtaDotData = new HashMap(); 333 334 // Loop over all the columns defined in the CRF file. 335 int col = 1; 336 aLine = in.readLine(); 337 while (aLine != null && !"".equals(aLine)) { 338 // Split the line using repeated spaces as a delimiter. 339 String[] parts = aLine.trim().split("\\s+"); 340 341 // Parse out the column number. 342 try { 343 String colStr = parts[0]; 344 col = Integer.parseInt(colStr) - 1; 345 } catch (NumberFormatException ex) { 346 throw new NoColumnNumberException(); 347 } 348 349 // Get the output type string. 350 String type = parts[1]; 351 352 // Get the array for the column in the DAT file we are working with. 353 arr = (ArrayParam)data.get(col); 354 switch (type) { 355 case "RSG": 356 // We have a function generator output. 357 arr = parseCRFElementOutput(parts, arr, 0, functionData, FUNCTION, nativeUnits); 358 break; 359 360 case "RC": 361 // We have a controller output. 362 arr = parseCRFElementOutput(parts, arr, 0, controllerOutData, CONTROLLER_OUT, nativeUnits); 363 break; 364 365 case "UC": 366 // We have a controller input column. 367 arr = parseCRFElementOutput(parts, arr, 0, controllerInData, CONTROLLER_IN, nativeUnits); 368 break; 369 370 case "RP": 371 // We have a sensor output. 372 arr = parseCRFElementOutput(parts, arr, 0, sensorData, SENSOR, nativeUnits); 373 break; 374 375 case "UP": 376 // We have an actuator output. 377 arr = parseCRFDestInput(parts, arr, actuatorData, ACTUATOR); 378 break; 379 380 case "DEMAG": 381 // We have device output. 382 arr = parseCRFElementOutput(parts, arr, 0, deviceData, DEVICE, nativeUnits); 383 break; 384 385 case "THETA": 386 // We have hinge angle output. 387 arr = parseCRFElementOutput(parts, arr, 0, thetaData, HINGE, nativeUnits); 388 break; 389 390 case "THDOT": 391 // We have hinge angular rate output. 392 arr = parseCRFElementOutput(parts, arr, 3, thDotData, HINGE, nativeUnits); 393 break; 394 395 case "WJI": 396 // We have angular body rate outputs. 397 arr = parseCRFElementOutput(parts, arr, 0, wjiData, BODY, nativeUnits); 398 break; 399 400 case "YJI": 401 // We have hinge translation outputs. 402 arr = parseCRFElementOutput(parts, arr, 6, htransData, HINGE, nativeUnits); 403 break; 404 405 case "YDOTJI": 406 case "YDJI": 407 // We have hinge velocity outputs. 408 arr = parseCRFElementOutput(parts, arr, 9, hvelData, HINGE, nativeUnits); 409 break; 410 411 case "ETA": 412 // We have generalized modal coordinate outputs. 413 arr = parseCRFElementOutput(parts, arr, 3, etaData, "ETA", nativeUnits); 414 break; 415 416 case "ETADOT": 417 // We have generalized modal velocity outputs. 418 arr = parseCRFElementOutput(parts, arr, 3, etaDotData, "ETADOT", nativeUnits); 419 break; 420 421 case "UDJBJ": 422 // We have flex body deformation outputs. 423 arr = parseCRFElementOutput(parts, arr, 3, udjbjData, "UDJBJ", nativeUnits); 424 break; 425 426 case "UDOTJ": 427 // We have flex body deformation rate outputs. 428 arr = parseCRFElementOutput(parts, arr, 3, udotjData, "UDOTJ", nativeUnits); 429 break; 430 431 case "PSIETA": 432 // We have flex body rotational deformation outputs. 433 arr = parseCRFElementOutput(parts, arr, 3, psiEtaData, "PSIETA", nativeUnits); 434 break; 435 436 case "PSETAD": 437 // We have flex body rotation rate outputs. 438 arr = parseCRFElementOutput(parts, arr, 3, psiEtaDotData, "PSETAD", nativeUnits); 439 break; 440 441 case "QBODY": 442 // We have a TREETOPS quaternion output. 443 arr = parseCRFElementOutput(parts, arr, 3, qbodyData, BODY, nativeUnits); 444 break; 445 446 case "Q_BODY": 447 // We have a CLVTOPS quaternion output. 448 arr = parseCRFElementOutput(parts, arr, 7, qbodyData, BODY, nativeUnits); 449 break; 450 451 case "RHJ": 452 // We have hinge location vector output. 453 arr = parseCRFElementOutput(parts, arr, 11, rvectData, BODY, nativeUnits); 454 break; 455 456 case "MASS": 457 // We have body mass output. 458 arr = parseCRFElementOutput(parts, arr, 14, massData, BODY, nativeUnits); 459 break; 460 461 case "CM": 462 // We have body center of mass outputs. 463 arr = parseCRFElementOutput(parts, arr, 15, cmData, BODY, nativeUnits); 464 break; 465 466 case "MOIPOI": 467 // We have body inertia outputs. 468 arr = parseCRFElementOutput(parts, arr, 18, moipoiData, BODY, nativeUnits); 469 break; 470 471 case "FCONI": 472 // We have contact force outputs. 473 arr = parseCRFElementOutput(parts, arr, 12, hcontFData, HINGE, nativeUnits); 474 break; 475 476 case "MCONI": 477 // We have contact moment outputs. 478 arr = parseCRFElementOutput(parts, arr, 15, hcontMData, HINGE, nativeUnits); 479 break; 480 481 case "FCJI": 482 // We have constraint force outputs. 483 arr = parseCRFElementOutput(parts, arr, 18, hconstFData, HINGE, nativeUnits); 484 break; 485 486 case "MCHJI": 487 // We have constraint moment outputs. 488 arr = parseCRFElementOutput(parts, arr, 21, hconstMData, HINGE, nativeUnits); 489 break; 490 491 case "NSTEPS": 492 // We have the number of steps completed column. 493 arr.setName("NSteps"); 494 arr.setUserObject(RESOURCES.getString("ttNStepsDesc")); 495 break; 496 497 case "UNSUN": 498 // We have solar direction unit vector components. 499 parseCRFSunUnitVector(parts, arr); 500 break; 501 502 default: 503 // We have an unknown type. 504 aLine = aLine.trim(); 505 aLine = aLine.substring(aLine.indexOf(" ")).trim(); 506 arr.setUserObject(aLine); 507 break; 508 } 509 510 // Replace the array in the DataSet with the one with updated units. 511 data.set(col, arr); 512 513 // Read in the next line. 514 aLine = in.readLine(); 515 } 516 517 } catch (Throwable ex) { 518 throw new IOException("Error parsing CRF file on line #" + in.getLineNumber() + ".", ex); 519 } 520 } 521 522 /** 523 * Parse model element output line from the CRF file to get the element ID number and 524 * output index. 525 * 526 * @param tokens An array of tokens parsed from a line of CRF file input. 527 * @param arr The array of data from the data file for this line of input. 528 * @param idxOffset The offset into the list of outputs from the element for this 529 * "type" of output. 530 * @param existingData A dictionary containing data on the elements of a particular type 531 * that have already been read in. 532 * @param type A constant representing the type of the data. 533 * @param nativeUnits The unit set representing the native units for this TreeTops 534 * model. 535 */ 536 private static ArrayParam parseCRFElementOutput(String[] tokens, ArrayParam arr, int idxOffset, 537 HashMap<String, List<String>> existingData, String type, UnitSet nativeUnits) throws IOException, NumberFormatException { 538 // Get the output index number or element type of this type of item. 539 String outIdxStr = tokens[2]; 540 541 // Get the element ID. 542 String idStr = "?"; 543 if (tokens.length > 3) { 544 idStr = tokens[3]; 545 546 // Make sure it is actually a number (an exception is thrown if it is not). 547 Integer.parseInt(idStr); 548 } 549 550 // Determine the index of the output in this column. 551 int outputIdx; 552 if (!"?".equals(idStr)) { 553 List<String> existing = existingData.get(idStr); 554 if (existing == null) { 555 existing = new ArrayList(); 556 existingData.put(idStr, existing); 557 } 558 existing.add(idStr); 559 outputIdx = existing.size() - 1 + idxOffset; // Make the index 0 offset. 560 } else 561 outputIdx = Integer.parseInt(outIdxStr) - 1; // Make index 0 offset. 562 563 String desc, var; 564 Unit unit = Dimensionless.UNIT; 565 switch (type) { 566 case BODY: 567 case HINGE: { 568 // Get the output description for this output. 569 desc = OUT_DESC.get(type)[outputIdx]; 570 // Get the output variable name. 571 var = OUT_VARS.get(type)[outputIdx]; 572 // Get the output unit type. 573 String unitType = OUT_UNITS.get(type)[outputIdx]; 574 // Get the output units from the unit type and this model's native units. 575 unit = getUnitFromType(nativeUnits, unitType); 576 break; 577 } 578 case "ETA": { 579 // Skip the MODE column (idx = 4) 580 // Get the MODE ID #. 581 String mIDStr = tokens[5].trim(); 582 desc = MessageFormat.format(RESOURCES.getString("ttFlexBodyModeIDDesc"),mIDStr); 583 var = "ETA_" + mIDStr; 584 type = BODY; 585 break; 586 } 587 case "UDJBJ": { 588 // Skip the NODE column (idx = 4). 589 // Get the NODE ID #. 590 String nIDStr = tokens[5].trim(); 591 // Skip the AXIS column (idx = 6). 592 // Get the Axis ID#. 593 String axisIDStr = tokens[7].trim(); 594 int axis = Integer.parseInt(axisIDStr); 595 desc = MessageFormat.format(RESOURCES.getString("ttFlexBodyDispDesc"),nIDStr); 596 var = "n" + nIDStr + ":UDJBJ_" + (axis == 1 ? "X" : (axis == 2 ? "Y" : "Z")); 597 unit = nativeUnits.length(); 598 type = BODY; 599 break; 600 } 601 case "UDOTJ": { 602 // Skip the NODE column (idx = 4). 603 // Get the NODE ID #. 604 String nIDStr = tokens[5].trim(); 605 // Skip the AXIS column (idx = 6). 606 // Get the AXIS ID #. 607 String axisIDStr = tokens[7].trim(); 608 int axis = Integer.parseInt(axisIDStr); 609 desc = MessageFormat.format(RESOURCES.getString("ttFlexBodyDispRateDesc"),nIDStr); 610 var = "n" + nIDStr + ":UDOTJ_" + (axis == 1 ? "X" : (axis == 2 ? "Y" : "Z")); 611 unit = nativeUnits.velocity(); 612 type = BODY; 613 break; 614 } 615 case "PSIETA": { 616 // Skip the NODE column (idx = 4). 617 // Get the NODE ID #. 618 String nIDStr = tokens[5].trim(); 619 // Skip the AXIS column (idx = 6). 620 // Get the AXIS ID #. 621 String axisIDStr = tokens[7].trim(); 622 int axis = Integer.parseInt(axisIDStr); 623 desc = MessageFormat.format(RESOURCES.getString("ttFlexBodyRotDesc"),nIDStr); 624 var = "n" + nIDStr + ":PSIETA_" + (axis == 1 ? "X" : (axis == 2 ? "Y" : "Z")); 625 unit = SI.RADIAN; 626 type = BODY; 627 break; 628 } 629 case "PSETAD": { 630 // Skip the NODE column (idx = 4). 631 // Get the NODE ID #. 632 String nIDStr = tokens[5].trim(); 633 // Skip the AXIS column (idx = 6). 634 // Get the AXIS ID #. 635 String axisIDStr = tokens[7].trim(); 636 int axis = Integer.parseInt(axisIDStr); 637 desc = MessageFormat.format(RESOURCES.getString("ttFlexBodyRotRateDesc"),nIDStr); 638 var = "n" + nIDStr + ":PSETAD_" + (axis == 1 ? "X" : (axis == 2 ? "Y" : "Z")); 639 unit = AngularVelocity.UNIT; 640 type = BODY; 641 break; 642 } 643 case CONTROLLER_IN: 644 desc = MessageFormat.format(RESOURCES.getString("ttControllerInputDesc"), outputIdx + 1); 645 var = "Input" + (outputIdx + 1); 646 break; 647 case CONTROLLER_OUT: 648 desc = MessageFormat.format(RESOURCES.getString("ttControllerOutputDesc"), outputIdx + 1); 649 var = "Output" + (outputIdx + 1); 650 break; 651 default: 652 desc = MessageFormat.format(RESOURCES.getString("ttOutputDesc"), outputIdx + 1); 653 var = "Output" + (outputIdx + 1); 654 break; 655 } 656 657 // Construct a name from the information about this column. 658 StringBuilder name = new StringBuilder(); 659 name.append(IDABR.get(type)); 660 name.append(":"); 661 name.append(idStr); 662 name.append(":"); 663 name.append(var); 664 665 // Set the name, units, and reference data. 666 arr.setName(name.toString()); 667 arr = arr.changeTo(unit); 668 arr.setUserObject(desc); 669 670 return arr; 671 } 672 673 /** 674 * Parse an interconnect destination input line from the CRF file to get the element 675 * ID number and output index. 676 * 677 * @param tokens An array of tokens parsed from a line of CRF file input. 678 * @param arr The array of data from the DAT file for this line of input. 679 * @param existingData A dictionary containing data on the destinations that have 680 * already been read in. 681 * @param type A constant representing the type of the data. 682 */ 683 private static ArrayParam parseCRFDestInput(String[] tokens, ArrayParam arr, 684 HashMap<String,List<String>> existingData, String type) { 685 686 // The output index number or element type. 687 String inputIdxStr = tokens[2].trim(); 688 689 // Get the element ID. 690 String idStr = "?"; 691 if (tokens.length > 3) { 692 idStr = tokens[3].trim(); 693 694 // Make sure it is actually a number (an exception thrown if it is not). 695 Integer.parseInt(idStr); 696 } 697 698 // Determine the index of the input in this column. 699 int inputIdx; 700 if (!"?".equals(idStr)) { 701 List<String> existing = existingData.get(idStr); 702 if (existing == null) { 703 existing = new ArrayList(); 704 existingData.put(idStr, existing); 705 } 706 existing.add(idStr); 707 inputIdx = existing.size() - 1; // Make index 0 offset. 708 709 } else 710 inputIdx = Integer.parseInt(inputIdxStr) - 1; // Make index 0 offset. 711 712 // Get the description for this input. 713 String desc = MessageFormat.format(RESOURCES.getString("ttInputParDesc"), inputIdx + 1); 714 715 // Get the input variable name. 716 String var = "Input" + (inputIdx + 1); 717 718 // Get the input units from the unit type and this model's native units. 719 Unit unit = Dimensionless.UNIT; 720 721 // Construct a name from the information about this column. 722 StringBuilder name = new StringBuilder(); 723 name.append(IDABR.get(type)); 724 name.append(":"); 725 name.append(idStr); 726 name.append(":"); 727 name.append(var); 728 729 // Set the name, units, and reference data. 730 arr.setName(name.toString()); 731 arr = arr.changeTo(unit); 732 arr.setUserObject(desc); 733 734 return arr; 735 } 736 737 /** 738 * Parse a Sun unit vector line from the CRF file. 739 * 740 * @param tokens An array of tokens parsed from a line of CRF file input. 741 * @param arr The array of data from the DAT file for this line of input. 742 */ 743 private static void parseCRFSunUnitVector(String[] tokens, ArrayParam arr) 744 throws NoSuchElementException, NumberFormatException { 745 // Skip the "INERTIAL", "COMPONENTS," and "AXIS" tokens (idx = 2, 3 & 4). 746 747 // Get the axis index number. 748 String axisStr = tokens[5].trim(); 749 int axis = Integer.parseInt(axisStr) - 1; // Make zero offset index. 750 751 String[] axisOptions = {"USX", "USY", "USZ"}; 752 753 // Construct a list of information about this column. 754 StringBuilder name = new StringBuilder(); 755 name.append("UNSUN"); 756 name.append(":"); 757 name.append(axisOptions[axis]); 758 759 // Set the name and reference data. 760 arr.setName(name.toString()); 761 arr.setUserObject(RESOURCES.getString("ttSunUVDesc")); 762 } 763 764 /** 765 * Method that returns the unit from the provided unit set of the specified type. 766 */ 767 private static Unit getUnitFromType(UnitSet unitSet, String type) throws IOException { 768 // Deal with some special cases first. 769 if (null == type) 770 return Dimensionless.UNIT; 771 else switch (type) { 772 case "nd": 773 return Dimensionless.UNIT; 774 case "angularVelocityR": 775 return AngularVelocity.UNIT; 776 case "angleR": 777 return SI.RADIAN; 778 default: 779 break; 780 } 781 782 // Use reflection to get the unit type from the unit set. 783 // "type" is the name of the method in unitSet to call. 784 Unit unit = Dimensionless.UNIT; 785 try { 786 // Get the appropriate method from unitSet. 787 Method method = unitSet.getClass().getMethod( type, (Class[])null ); 788 789 // Get the unit of the specified type. 790 unit = (Unit)method.invoke(unitSet, (Object[])null); 791 792 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 793 throw new IOException(e); 794 } 795 796 return unit; 797 } 798 799 /** 800 * Sets the default set name to use. 801 * 802 * @param name The name to use as the default set name. 803 */ 804 @Override 805 public void setDefaultSetName(CharSequence name) { 806 datReader.setDefaultSetName(name); 807 } 808 809 /** 810 * This class can not write to the CLVTOPS format file(s), so this method always throws 811 * an exception. 812 * 813 * @param parent the parent frame 814 * @param data the data to be selected from. 815 * @return the selected data. 816 * @throws UnsupportedOperationException always thrown 817 */ 818 @Override 819 public List<DataSet> selectDataForSaving(Frame parent, List<DataSet> data) { 820 throw new UnsupportedOperationException("Not supported yet."); 821 } 822 823 /** 824 * Method that writes out the data stored in the specified {@link DataSet} object to 825 * the specified output stream in CLVTOPS format. This method will throw an exception 826 * as writing to this format is not supported. 827 * 828 * @param output The output stream to which the data is to be written. 829 * @param data A list of {@link DataSet} objects containing data to be written out. 830 * @throws IOException If there is a problem writing to the specified stream. 831 * @throws UnsupportedOperationException always thrown 832 */ 833 @Override 834 public void write(OutputStream output, List<DataSet> data) throws IOException { 835 throw new UnsupportedOperationException("Not supported yet."); 836 } 837 838 /** 839 * A simple exception to catch the case of the column number missing in the CRF file. 840 */ 841 @SuppressWarnings("serial") 842 private static class NoColumnNumberException extends Exception { 843 844 public NoColumnNumberException() { 845 } 846 } 847 848}