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