package org.yamcs.xtce; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteOrder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.yamcs.utils.StringConverter; import org.yamcs.xtce.CheckWindow.TimeWindowIsRelativeToType; import org.yamcs.xtce.CommandVerifier.TerminationAction; import org.yamcs.xtce.NameReference.ResolvedAction; import org.yamcs.xtce.NameReference.Type; import org.yamcs.xtce.SequenceEntry.ReferenceLocationType; import org.yamcs.xtce.xml.XtceAliasSet; import org.yamcs.xtceproc.JavaExpressionCalibratorFactory; import com.google.common.primitives.UnsignedLongs; import jxl.Cell; import jxl.CellType; import jxl.DateCell; import jxl.NumberCell; import jxl.Sheet; import jxl.Workbook; import jxl.WorkbookSettings; import jxl.read.biff.BiffException; /** * This class loads database from excel spreadsheets. Used for the Solar instruments for which the TM * database is too complicated to store in the MDB. * * @author nm, ddw * */ public class SpreadsheetLoader extends AbstractFileLoader { protected HashMap<String, Calibrator> calibrators = new HashMap<>(); protected HashMap<String, String> javaFormulas = new HashMap<>(); protected HashMap<String, EnumerationDefinition> enumerations = new HashMap<>(); protected HashMap<String, Parameter> parameters = new HashMap<>(); protected HashSet<Parameter> outputParameters = new HashSet<>(); // Outputs to algorithms protected HashSet<PotentialExtractionError> potentialErrors = new HashSet<>(); protected SpreadsheetLoadContext ctx = new SpreadsheetLoadContext(); //sheet names protected static final String SHEET_GENERAL = "General"; protected static final String SHEET_CHANGELOG = "ChangeLog"; protected static final String SHEET_CALIBRATION = "Calibration"; protected static final String SHEET_TELEMETERED_PARAMETERS = "Parameters"; protected static final String SHEET_LOCAL_PARAMETERS = "LocalParameters"; protected static final String SHEET_DERIVED_PARAMETERS = "DerivedParameters"; protected static final String SHEET_CONTAINERS = "Containers"; protected static final String SHEET_ALGORITHMS = "Algorithms"; protected static final String SHEET_ALARMS = "Alarms"; protected static final String SHEET_COMMANDS = "Commands"; protected static final String SHEET_COMMANDOPTIONS = "CommandOptions"; protected static final String SHEET_COMMANDVERIFICATION = "CommandVerification"; //the list of sheets that can be part of subsystems with a sub1/sub2/sub3/SheetName notation static String[] SUBSYSTEM_SHEET_NAMES = {SHEET_CALIBRATION, SHEET_TELEMETERED_PARAMETERS, SHEET_LOCAL_PARAMETERS, SHEET_DERIVED_PARAMETERS, SHEET_CONTAINERS, SHEET_ALGORITHMS, SHEET_ALARMS, SHEET_COMMANDS, SHEET_COMMANDOPTIONS, SHEET_COMMANDVERIFICATION}; //columns in the parameters sheet (including local parameters) static final int IDX_PARAM_NAME = 0; static final int IDX_PARAM_BITLENGTH = 1; static final int IDX_PARAM_RAWTYPE = 2; static final int IDX_PARAM_ENGTYPE = 3; static final int IDX_PARAM_ENGUNIT = 4; static final int IDX_PARAM_CALIBRATION = 5; static final int IDX_PARAM_DESCRIPTION = 6; //columns in the containers sheet static final int IDX_CONT_NAME = 0; static final int IDX_CONT_PARENT = 1; static final int IDX_CONT_CONDITION = 2; static final int IDX_CONT_FLAGS = 3; static final int IDX_CONT_PARA_NAME = 4; static final int IDX_CONT_RELPOS = 5; static final int IDX_CONT_SIZEINBITS = 6; static final int IDX_CONT_EXPECTED_INTERVAL = 7; static final int IDX_CONT_DESCRIPTION = 8; //columns in calibrations sheet static final int IDX_CALIB_NAME = 0; static final int IDX_CALIB_TYPE = 1; static final int IDX_CALIB_CALIB1 = 2; static final int IDX_CALIB_CALIB2 = 3; //columns in the algorithms sheet static final int IDX_ALGO_NAME = 0; static final int IDX_ALGO_LANGUGAGE = 1; static final int IDX_ALGO_TEXT = 2; static final int IDX_ALGO_TRIGGER = 3; static final int IDX_ALGO_PARA_INOUT=4; static final int IDX_ALGO_PARA_REF = 5; static final int IDX_ALGO_PARA_INSTANCE = 6; static final int IDX_ALGO_PARA_NAME = 7; static final int IDX_ALGO_PARA_FLAGS = 8; //columns in the alarms sheet static final int IDX_ALARM_PARAM_NAME = 0; static final int IDX_ALARM_CONTEXT = 1; static final int IDX_ALARM_REPORT = 2; static final int IDX_ALARM_MIN_VIOLATIONS = 3; static final int IDX_ALARM_WATCH_TRIGGER = 4; static final int IDX_ALARM_WATCH_VALUE = 5; static final int IDX_ALARM_WARNING_TRIGGER = 6; static final int IDX_ALARM_WARNING_VALUE = 7; static final int IDX_ALARM_DISTRESS_TRIGGER = 8; static final int IDX_ALARM_DISTRESS_VALUE = 9; static final int IDX_ALARM_CRITICAL_TRIGGER = 10; static final int IDX_ALARM_CRITICAL_VALUE = 11; static final int IDX_ALARM_SEVERE_TRIGGER = 12; static final int IDX_ALARM_SEVERE_VALUE = 13; //columns in the processed parameters sheet protected static final int IDX_PP_UMI = 0; protected static final int IDX_PP_GROUP = 1; protected static final int IDX_PP_ALIAS = 2; //columns in the command sheet protected static final int IDX_CMD_NAME = 0; protected static final int IDX_CMD_PARENT = 1; protected static final int IDX_CMD_ARG_ASSIGNMENT = 2; protected static final int IDX_CMD_FLAGS = 3; protected static final int IDX_CMD_ARGNAME = 4; protected static final int IDX_CMD_RELPOS = 5; protected static final int IDX_CMD_SIZEINBITS = 6; protected static final int IDX_CMD_ENGTYPE = 7; protected static final int IDX_CMD_RAWTYPE = 8; protected static final int IDX_CMD_DEFVALUE = 9; protected static final int IDX_CMD_ENGUNIT = 10; protected static final int IDX_CMD_CALIBRATION = 11; protected static final int IDX_CMD_RANGELOW = 12; protected static final int IDX_CMD_RANGEHIGH = 13; protected static final int IDX_CMD_DESCRIPTION = 14; //columns in the command options sheet protected static final int IDX_CMDOPT_NAME = 0; protected static final int IDX_CMDOPT_TXCONST = 1; protected static final int IDX_CMDOPT_TXCONST_TIMEOUT = 2; protected static final int IDX_CMDOPT_SIGNIFICANCE = 3; protected static final int IDX_CMDOPT_SIGNIFICANCE_REASON = 4; //columns in the command verification sheet protected static final int IDX_CMDVERIF_NAME = 0; protected static final int IDX_CMDVERIF_STAGE = 1; protected static final int IDX_CMDVERIF_TYPE = 2; protected static final int IDX_CMDVERIF_TEXT = 3; protected static final int IDX_CMDVERIF_CHECKWINDOW = 4; protected static final int IDX_CMDVERIF_CHECKWINDOW_RELATIVETO = 5; protected static final int IDX_CMDVERIF_ONSUCCESS = 6; protected static final int IDX_CMDVERIF_ONFAIL = 7; protected static final int IDX_CMDVERIF_ONTIMEOUT = 8; //columns in the changelog sheet protected static final int IDX_LOG_VERSION = 0; protected static final int IDX_LOG_DATE = 1; protected static final int IDX_LOG_MESSAGE = 2; protected static final String CALIB_TYPE_ENUMERATION = "enumeration"; protected static final String CALIB_TYPE_POLYNOMIAL = "polynomial"; protected static final String CALIB_TYPE_SPLINE = "spline"; protected static final String CALIB_TYPE_JAVA_EXPRESSION = "java-expression"; protected static final String PARAM_ENGTYPE_STRING = "string"; protected static final String PARAM_ENGTYPE_BOOLEAN = "boolean"; protected static final String PARAM_ENGTYPE_BINARY = "binary"; protected static final String PARAM_ENGTYPE_ENUMERATED = "enumerated"; protected static final String PARAM_ENGTYPE_DOUBLE = "double"; protected static final String PARAM_ENGTYPE_UINT32 = "uint32"; protected static final String PARAM_ENGTYPE_INT32 = "int32"; protected static final String PARAM_ENGTYPE_UINT64 = "uint64"; protected static final String PARAM_ENGTYPE_INT64 = "int64"; protected static final String PARAM_ENGTYPE_FLOAT = "float"; // Increment major when breaking backward compatibility, increment minor when making backward compatible changes final static String FORMAT_VERSION="5.4"; // Explicitly support these versions (i.e. load without warning) final static String[] FORMAT_VERSIONS_SUPPORTED = new String[]{FORMAT_VERSION, "5.3"}; protected Workbook workbook; protected SpaceSystem rootSpaceSystem; String fileFormatVersion; /* * configSection is the name under which this config appears in the database */ public SpreadsheetLoader(String filename) { super(filename); ctx.file=new File(filename).getName(); } @Override public String getConfigName(){ return ctx.file; } @Override public SpaceSystem load() { log.info("Loading spreadsheet {}", path); try { // Given path may be relative, so use absolute path to report issues File ssFile = new File(path); if(!ssFile.exists()) { throw new FileNotFoundException(ssFile.getAbsolutePath()); } WorkbookSettings ws = new WorkbookSettings(); ws.setEncoding("Cp1252"); workbook = Workbook.getWorkbook(ssFile, ws); } catch (BiffException|IOException e) { throw new SpreadsheetLoadException(ctx, e); } try { loadSheets(); } catch(SpreadsheetLoadException e) { throw e; } catch (Exception e) { throw new SpreadsheetLoadException(ctx, e); } // Check errors after all sheets have been read for(PotentialExtractionError e : potentialErrors) { e.recheck(); } return rootSpaceSystem; } protected void loadSheets() throws SpreadsheetLoadException { loadGeneralSheet(true); loadChangelogSheet(false); //filter all sheets with names ending in the standard names SUBSYSTEM_SHEET_NAMES List<String> relevantSheets = Arrays.stream(workbook.getSheetNames()).filter(sheetName -> { return Arrays.stream(SUBSYSTEM_SHEET_NAMES).filter(s -> sheetName.endsWith(s)).findAny().isPresent(); }).collect(Collectors.toList()); //create all subsystems for(String s: relevantSheets) { String[] a = s.split("\\|"); SpaceSystem ss = rootSpaceSystem; for(int i=0; i<a.length-1; i++) { SpaceSystem ss1 = ss.getSubsystem(a[i]); if(ss1==null) { log.debug("Creating subsystem '{}'", a[i]); ss1 = new SpaceSystem(a[i]); ss.addSpaceSystem(ss1); } ss = ss1; } } loadSpaceSystem("", rootSpaceSystem); } protected void loadSpaceSystem(String sheetNamePrefix, SpaceSystem spaceSystem) { loadCalibrationSheet(spaceSystem, sheetNamePrefix + SHEET_CALIBRATION); loadParametersSheet(spaceSystem, sheetNamePrefix + SHEET_TELEMETERED_PARAMETERS, DataSource.TELEMETERED); loadParametersSheet(spaceSystem, sheetNamePrefix + SHEET_DERIVED_PARAMETERS, DataSource.DERIVED); loadParametersSheet(spaceSystem, sheetNamePrefix + SHEET_LOCAL_PARAMETERS, DataSource.LOCAL); loadContainersSheet(spaceSystem, sheetNamePrefix + SHEET_CONTAINERS); loadAlgorithmsSheet(spaceSystem, sheetNamePrefix + SHEET_ALGORITHMS); loadAlarmsSheet(spaceSystem, sheetNamePrefix + SHEET_ALARMS); loadCommandSheet(spaceSystem, sheetNamePrefix + SHEET_COMMANDS); loadCommandOptionsSheet(spaceSystem, sheetNamePrefix + SHEET_COMMANDOPTIONS); loadCommandVerificationSheet(spaceSystem, sheetNamePrefix + SHEET_COMMANDVERIFICATION); for(SpaceSystem ss: spaceSystem.getSubSystems()) { String prefix = sheetNamePrefix.isEmpty()?ss.getName()+"|": sheetNamePrefix+ss.getName()+"|"; loadSpaceSystem(prefix, ss); } } protected void loadGeneralSheet(boolean required) { Sheet sheet = switchToSheet(SHEET_GENERAL, required); if(sheet==null){ return; } Cell[] cells=jumpToRow(sheet, 1); // Version check String version = cells[0].getContents(); // Specific versions supported boolean supported = false; for( String supportedVersion:FORMAT_VERSIONS_SUPPORTED ) { if( version.equals( supportedVersion ) ) { supported = true; } } // If not explicitly supported, check major version number... if( !supported ) { String sheetCompatVersion = version.substring( 0, version.indexOf('.') ); String loaderCompatVersion = FORMAT_VERSION.substring( 0, FORMAT_VERSION.indexOf('.')); supported = loaderCompatVersion.equals( sheetCompatVersion ); // If major version number matches, but minor number differs if( supported && !FORMAT_VERSION.equals( version ) ) { log.info("Some spreadsheet features for '{}' may not be supported by this loader: " + "Spreadsheet version (%) differs from loader supported version (%s)", ctx.file, version, FORMAT_VERSION); } } if( !supported ) { throw new SpreadsheetLoadException(ctx, String.format( "Format version (%s) not supported by loader version (%s)", version, FORMAT_VERSION ) ); } fileFormatVersion = version; String name=requireString(cells, 1); rootSpaceSystem = new SpaceSystem(name); // Add a header Header header = new Header(); rootSpaceSystem.setHeader( header ); if( cells.length >= 3 ) { header.setVersion( cells[2].getContents() ); } try { File wbf = new File( path ); Date d = new Date( wbf.lastModified() ); String date = (new SimpleDateFormat("yyyy/DDD HH:mm:ss")).format( d ); header.setDate( date ); } catch ( Exception e ) { // Ignore } } protected void loadCalibrationSheet(SpaceSystem spaceSystem, String sheetName) { //read the calibrations Sheet sheet = switchToSheet(sheetName, false); if(sheet==null) { return; } double[] pol_coef = null; // SplinePoint = pointpair ArrayList<SplinePoint>spline = null; EnumerationDefinition enumeration = null; // start at 1 to not use the first line (= title line) int start = 1; while(true) { // we first search for a row containing (= starting) a new calibration while (start < sheet.getRows()) { Cell[] cells = jumpToRow(sheet, start); if ((cells.length > 0) && (cells[0].getContents().length() > 0) && !cells[0].getContents().startsWith("#")) { break; } start++; } if (start >= sheet.getRows()) { break; } Cell[] cells = jumpToRow(sheet, start); String name = cells[IDX_CALIB_NAME].getContents(); String type = cells[IDX_CALIB_TYPE].getContents(); // now we search for the matching last row of that calibration int end = start + 1; while (end < sheet.getRows()) { cells = jumpToRow(sheet, end); if (!hasColumn(cells, IDX_CALIB_CALIB1)) { break; } end++; } if ("pointpair".equalsIgnoreCase(type)) { type = CALIB_TYPE_SPLINE; } if (CALIB_TYPE_ENUMERATION.equalsIgnoreCase(type)) { enumeration = new EnumerationDefinition(); } else if (CALIB_TYPE_POLYNOMIAL.equalsIgnoreCase(type)) { pol_coef = new double[end - start]; } else if (CALIB_TYPE_SPLINE.equalsIgnoreCase(type)) { spline = new ArrayList<>(); } else if (CALIB_TYPE_JAVA_EXPRESSION.equalsIgnoreCase(type)) { cells = jumpToRow(sheet, start); if(end!=start+1) { throw new SpreadsheetLoadException(ctx, "Java formula must be specified on one line"); } if(isEmpty(cells[IDX_CALIB_CALIB1])) { throw new SpreadsheetLoadException(ctx, "Java formula must be specified on the CALIB1 column"); } String javaFormula = cells[IDX_CALIB_CALIB1].getContents(); javaFormulas.put(name, javaFormula); start = end; } else { throw new SpreadsheetLoadException(ctx, "Calibration type '"+type+"' not supported. Supported types: " +Arrays.asList(CALIB_TYPE_ENUMERATION, CALIB_TYPE_POLYNOMIAL, CALIB_TYPE_SPLINE, CALIB_TYPE_JAVA_EXPRESSION)); } for (int j = start; j < end; j++) { cells = jumpToRow(sheet, j); if (CALIB_TYPE_ENUMERATION.equalsIgnoreCase(type)) { try { long raw=Integer.decode(cells[IDX_CALIB_CALIB1].getContents()); enumeration.valueMap.put(raw, cells[IDX_CALIB_CALIB2].getContents()); } catch(NumberFormatException e) { throw new SpreadsheetLoadException(ctx, "Can't get integer from raw value out of '"+cells[IDX_CALIB_CALIB1].getContents()+"'"); } } else if (CALIB_TYPE_POLYNOMIAL.equalsIgnoreCase(type)) { pol_coef[j - start] = getNumber(cells[IDX_CALIB_CALIB1]); } else if (CALIB_TYPE_SPLINE.equalsIgnoreCase(type)) { spline.add(new SplinePoint(getNumber(cells[IDX_CALIB_CALIB1]), getNumber(cells[IDX_CALIB_CALIB2]))); } } if (CALIB_TYPE_ENUMERATION.equalsIgnoreCase(type)) { enumerations.put(name, enumeration); } else if (CALIB_TYPE_POLYNOMIAL.equalsIgnoreCase(type)) { calibrators.put(name, new PolynomialCalibrator(pol_coef)); } else if (CALIB_TYPE_SPLINE.equalsIgnoreCase(type)) { calibrators.put(name, new SplineCalibrator(spline)); } start = end; } } private double getNumber(Cell cell) { if((cell.getType()==CellType.NUMBER) || (cell.getType()==CellType.NUMBER_FORMULA)) { return ((NumberCell) cell).getValue(); } else { return Double.parseDouble(cell.getContents()); } } protected void loadParametersSheet(SpaceSystem spaceSystem, String sheetName, DataSource dataSource) { Sheet sheet = switchToSheet(sheetName, false); if(sheet==null){ return; } Cell[] firstRow = jumpToRow(sheet, 0); for (int i = 1; i < sheet.getRows(); i++) { Cell[] cells = jumpToRow(sheet, i); if ((cells == null) || (cells.length < 3) || cells[0].getContents().startsWith("#")) { continue; } String name = cells[IDX_PARAM_NAME].getContents(); if (name.length() == 0) { continue; } validateNameType(name); final Parameter param = new Parameter(name); parameters.put(param.getName(), param); XtceAliasSet xas = getAliases(firstRow, cells); if(xas!=null) { param.setAliasSet(xas); } spaceSystem.addParameter(param); String rawtype = cells[IDX_PARAM_RAWTYPE].getContents(); if("DerivedValue".equalsIgnoreCase(rawtype)) { continue; } int bitlength=-1; try { String bitls=cells[IDX_PARAM_BITLENGTH].getContents(); if(!bitls.isEmpty()) { bitlength = Integer.decode(bitls); } } catch(NumberFormatException e) { throw new SpreadsheetLoadException(ctx, "Can't get bitlength out of '"+cells[IDX_PARAM_BITLENGTH].getContents()+"'"); } String engtype = cells[IDX_PARAM_ENGTYPE].getContents(); String calib=null; if(hasColumn(cells, IDX_PARAM_CALIBRATION)) { calib = cells[IDX_PARAM_CALIBRATION].getContents(); } if(hasColumn(cells, IDX_PARAM_DESCRIPTION)) { String shortDescription = cells[IDX_PARAM_DESCRIPTION].getContents(); param.setShortDescription(shortDescription); } if("n".equals(calib) || "".equals(calib)){ calib=null; } else if("y".equalsIgnoreCase(calib)) { calib=name; } if("uint".equalsIgnoreCase(engtype)) { engtype = PARAM_ENGTYPE_UINT32; } else if("int".equalsIgnoreCase(engtype)) { engtype = PARAM_ENGTYPE_INT32; } ParameterType ptype = null; if (PARAM_ENGTYPE_UINT32.equalsIgnoreCase(engtype)) { ptype = new IntegerParameterType(name); ((IntegerParameterType)ptype).signed = false; } else if (PARAM_ENGTYPE_UINT64.equalsIgnoreCase(engtype)) { ptype = new IntegerParameterType(name); ((IntegerParameterType)ptype).signed = false; ((IntegerParameterType)ptype).setSizeInBits(64); } else if (PARAM_ENGTYPE_INT32.equalsIgnoreCase(engtype)) { ptype = new IntegerParameterType(name); } else if(PARAM_ENGTYPE_INT64.equalsIgnoreCase(engtype)) { ptype = new IntegerParameterType(name); ((IntegerParameterType)ptype).setSizeInBits(64); } else if (PARAM_ENGTYPE_FLOAT.equalsIgnoreCase(engtype)) { ptype = new FloatParameterType(name); } else if (PARAM_ENGTYPE_DOUBLE.equalsIgnoreCase(engtype)) { ptype = new FloatParameterType(name); ((FloatParameterType)ptype).setSizeInBits(64); } else if (PARAM_ENGTYPE_ENUMERATED.equalsIgnoreCase(engtype)) { if(calib==null) { throw new SpreadsheetLoadException(ctx, "Parameter " + name + " has to have an enumeration"); } EnumerationDefinition enumeration = enumerations.get(calib); if (enumeration == null) { throw new SpreadsheetLoadException(ctx, "Parameter " + name + " is supposed to have an enumeration '" + calib + "' but the enumeration does not exist"); } ptype = new EnumeratedParameterType(calib); for (Entry<Long,String> entry:enumeration.valueMap.entrySet()) { ((EnumeratedParameterType) ptype).addEnumerationValue(entry.getKey(), entry.getValue()); } } else if (PARAM_ENGTYPE_STRING.equalsIgnoreCase(engtype)) { ptype = new StringParameterType(name); } else if (PARAM_ENGTYPE_BOOLEAN.equalsIgnoreCase(engtype)) { ptype = new BooleanParameterType(name); } else if (PARAM_ENGTYPE_BINARY.equalsIgnoreCase(engtype)) { ptype = new BinaryParameterType(name); } else { if(engtype.isEmpty()) { throw new SpreadsheetLoadException(ctx, "No engineering type specified"); } else { throw new SpreadsheetLoadException(ctx, "Unknown parameter type '" + engtype+"'"); } } String units=null; if(cells.length>IDX_PARAM_ENGUNIT) { units = cells[IDX_PARAM_ENGUNIT].getContents(); } if(!"".equals(units) && units != null && ptype instanceof BaseDataType) { UnitType unitType = new UnitType(units); ((BaseDataType) ptype).addUnit(unitType); } DataEncoding encoding = getDataEncoding(ctx, param, rawtype, engtype, bitlength, calib); if (ptype instanceof IntegerParameterType) { // Integers can be encoded as strings if( encoding instanceof StringDataEncoding ) { // Create a new int encoding which uses the configured string encoding IntegerDataEncoding intStringEncoding = new IntegerDataEncoding(name, ((StringDataEncoding)encoding)); if( calib != null ) { Calibrator c = calibrators.get(calib); if( c == null ) { throw new SpreadsheetLoadException(ctx, "Parameter " + name + " specified calibrator '" + calib + "' but the calibrator does not exist"); } intStringEncoding.defaultCalibrator = c; } ((IntegerParameterType)ptype).encoding = intStringEncoding; } else { ((IntegerParameterType)ptype).encoding = encoding; } } else if (ptype instanceof BinaryParameterType) { ((BinaryParameterType)ptype).encoding = encoding; } else if (ptype instanceof FloatParameterType) { // Floats can be encoded as strings if ( encoding instanceof StringDataEncoding ) { // Create a new float encoding which uses the configured string encoding FloatDataEncoding floatStringEncoding = new FloatDataEncoding(((StringDataEncoding)encoding) ); if(calib!=null) { Calibrator c = calibrators.get(calib); if( c == null ) { throw new SpreadsheetLoadException(ctx, "Parameter " + name + " specified calibrator '" + calib + "' but the calibrator does not exist."); } else { floatStringEncoding.defaultCalibrator = c; } } ((FloatParameterType)ptype).encoding = floatStringEncoding; } else { ((FloatParameterType)ptype).encoding = encoding; } } else if (ptype instanceof EnumeratedParameterType) { if(((EnumeratedParameterType) ptype).getEncoding() != null) { // Some other param has already led to setting the encoding of this shared ptype. // Do some basic consistency checks if(((EnumeratedParameterType) ptype).getEncoding().getSizeInBits() != encoding.getSizeInBits()) { throw new SpreadsheetLoadException(ctx, "Multiple parameters are sharing calibrator '"+calib+"' with different bit sizes."); } } // Enumerations encoded as string integers if( encoding instanceof StringDataEncoding ) { IntegerDataEncoding intStringEncoding = new IntegerDataEncoding(name, ((StringDataEncoding)encoding)); // Don't set calibrator, already done when making ptype ((EnumeratedParameterType) ptype).encoding = intStringEncoding; } else { ((EnumeratedParameterType) ptype).encoding = encoding; } } else if (ptype instanceof StringParameterType) { ((StringParameterType)ptype).encoding = encoding; } else if (ptype instanceof BooleanParameterType) { ((BooleanParameterType)ptype).encoding = encoding; } param.setParameterType(ptype); param.setDataSource(dataSource); } } DataEncoding getDataEncoding(SpreadsheetLoadContext ctx, Parameter param, String rawtype, String engtype, int bitlength, String calib) { String name = param.getName(); DataEncoding encoding = null; if (("uint".equalsIgnoreCase(rawtype)) || rawtype.toLowerCase().startsWith("int")) { if(bitlength==-1) { potentialErrors.add(new PotentialExtractionError(ctx, "Bit length is mandatory for integer parameters") { @Override public boolean errorPersists() { return !outputParameters.contains(param); } }); } encoding = new IntegerDataEncoding(bitlength); if (rawtype.toLowerCase().startsWith("int")) { if ("int".equals(rawtype)) { ((IntegerDataEncoding)encoding).encoding = IntegerDataEncoding.Encoding.twosComplement; } else { int startBracket = rawtype.indexOf('('); if (startBracket != -1) { int endBracket = rawtype.indexOf(')', startBracket); if (endBracket != -1) { String intRepresentation = rawtype.substring(startBracket+1, endBracket).trim().toLowerCase(); if ("2c".equals(intRepresentation)) { ((IntegerDataEncoding)encoding).encoding = IntegerDataEncoding.Encoding.twosComplement; } else if ("si".equals(intRepresentation)) { ((IntegerDataEncoding)encoding).encoding = IntegerDataEncoding.Encoding.signMagnitude; } else { throw new SpreadsheetLoadException(ctx, "Unsupported signed integer representation: "+intRepresentation); } } } } } if ((!PARAM_ENGTYPE_ENUMERATED.equalsIgnoreCase(engtype)) && (calib!=null)) { Calibrator c = calibrators.get(calib); if (c == null) { String msg = "Parameter " + name + " is supposed to have a calibrator '" + calib + "' but the calibrator does not exist."; if(enumerations.containsKey(calib)) { msg+=" Instead an enumeration with this name exists."; } throw new SpreadsheetLoadException(ctx, msg); } ((IntegerDataEncoding)encoding).defaultCalibrator = c; } } else if ("bytestream".equalsIgnoreCase(rawtype)) { if(bitlength==-1) { throw new SpreadsheetLoadException(ctx, "Bit length is mandatory for bytestream parameters"); } encoding = new BinaryDataEncoding(name, bitlength); } else if ("boolean".equalsIgnoreCase(rawtype)) { if(bitlength!=-1) { throw new SpreadsheetLoadException(ctx, "Bit length is not allowed for boolean parameters (defaults to 1). Use any other raw type if you want to specify the bitlength"); } encoding=new BooleanDataEncoding(); } else if ("string".equalsIgnoreCase(rawtype)) { // Version <= 1.6 String type // STRING if(bitlength==-1) { // Assume null-terminated if no length specified encoding=new StringDataEncoding(name, StringDataEncoding.SizeType.TerminationChar); } else { encoding=new StringDataEncoding(name, StringDataEncoding.SizeType.Fixed); encoding.setSizeInBits(bitlength); } } else if ( "fixedstring".equalsIgnoreCase( rawtype ) ) { // v1.7 String type // FIXEDSTRING if(bitlength==-1) { throw new SpreadsheetLoadException(ctx, "Bit length is mandatory for fixedstring raw type"); } encoding = new StringDataEncoding(name, StringDataEncoding.SizeType.Fixed); encoding.setSizeInBits(bitlength); } else if ( rawtype.toLowerCase().startsWith( "terminatedstring" ) ) { // v1.7 String type // TERMINATEDSTRING encoding=new StringDataEncoding(name, StringDataEncoding.SizeType.TerminationChar); // Use specified byte if found, otherwise accept class default. int startBracket = rawtype.indexOf( '(' ); if( startBracket != -1 ) { int endBracket = rawtype.indexOf( ')', startBracket ); if( endBracket != -1 ) { try { byte terminationChar = Byte.parseByte( rawtype.substring( rawtype.indexOf('x', startBracket)+1, endBracket ).trim(), 16 ); ((StringDataEncoding)encoding).setTerminationChar(terminationChar); } catch (NumberFormatException e) { throw new SpreadsheetLoadException(ctx, "Could not parse specified base 16 terminator from "+rawtype); } } } } else if ( rawtype.toLowerCase().startsWith( "prependedsizestring" ) ) { // v1.7 String type // PREPENDEDSIZESTRING encoding=new StringDataEncoding( name, StringDataEncoding.SizeType.LeadingSize ); // Use specified size if found, otherwise accept class default. int startBracket = rawtype.indexOf( '(' ); if( startBracket != -1 ) { int endBracket = rawtype.indexOf( ')', startBracket ); if( endBracket != -1 ) { try { int sizeInBitsOfSizeTag = Integer.parseInt( rawtype.substring(startBracket+1, endBracket).trim() ); ((StringDataEncoding)encoding).setSizeInBitsOfSizeTag( sizeInBitsOfSizeTag ); } catch (NumberFormatException e) { throw new SpreadsheetLoadException(ctx, "Could not parse integer size from "+rawtype); } } } } else if ("float".equalsIgnoreCase(rawtype)) { if(bitlength==-1) { potentialErrors.add(new PotentialExtractionError(ctx, "Bit length is mandatory for float parameters") { @Override public boolean errorPersists() { return !outputParameters.contains(param); } }); } encoding = new FloatDataEncoding(bitlength); if((!PARAM_ENGTYPE_ENUMERATED.equalsIgnoreCase(engtype)) && calib!=null) { Calibrator c = getFloatCalibrator(name, calib); ((FloatDataEncoding)encoding).defaultCalibrator = c; } } else { // Raw type is optional if the parameter is not part of a container // However a calibration is associated to a raw type if(calib != null) { throw new SpreadsheetLoadException(ctx, "Parameter " + name + ": calibration specified without raw type"); } } return encoding; } private Calibrator getFloatCalibrator(String paraName, String calibName) { Calibrator c = calibrators.get(calibName); if (c != null) { return c; } String jf = javaFormulas.get(calibName); if(jf!=null) { JavaExpressionCalibrator jec = new JavaExpressionCalibrator(jf); try { JavaExpressionCalibratorFactory.compile(jec); } catch (IllegalArgumentException e) { throw new SpreadsheetLoadException(ctx, e.getMessage()); } return jec; } throw new SpreadsheetLoadException(ctx, "Parameter " + paraName + " is supposed to have a calibrator '" + calibName + "' but the calibrator does not exist."); } /** * Searches firstRow for all cells that start with "namespace:" and adds corresponding aliases * * @param firstRow * @param cells * @param xas */ private XtceAliasSet getAliases(Cell[] firstRow, Cell[] cells) { int n = Math.min(firstRow.length, cells.length); XtceAliasSet xas = null; for(int i=0; i<n; i++) { if(!isEmpty(firstRow[i]) && firstRow[i].getContents().startsWith("namespace:") && !isEmpty(cells[i])) { if(xas==null) { xas = new XtceAliasSet(); } String s = firstRow[i].getContents(); String namespace = s.substring(10, s.length()); String alias = cells[i].getContents(); xas.addAlias(namespace, alias); } } return xas; } boolean isEmpty(Cell cell) { return (cell==null) || (cell.getContents().isEmpty()); } protected void loadContainersSheet(SpaceSystem spaceSystem, String sheetName) { Sheet sheet = switchToSheet(sheetName, false); if(sheet==null){ return; } HashMap<String, SequenceContainer> containers = new HashMap<String, SequenceContainer>(); HashMap<String, String> parents = new HashMap<String, String>(); Cell[] firstRow = jumpToRow(sheet, 0); for (int i = 1; i < sheet.getRows(); i++) { // search for a new packet definition, starting from row i // (explanatory note, i is incremented inside this loop too, and that's why the following 4 lines work) Cell[] cells = jumpToRow(sheet, i); if (cells == null || cells.length<1) { log.trace("Ignoring line {} because it's empty",ctx.row); continue; } if(cells[0].getContents().equals("")|| cells[0].getContents().startsWith("#")) { log.trace("Ignoring line {} because first cell is empty or starts with '#'", ctx.row); continue; } // at this point, cells contains the data (name, path, ...) of either // a) a sub-container (inherits from another packet) // b) an aggregate container (which will be used as if it were a measurement, by other (sub)containers) String name = cells[IDX_CONT_NAME].getContents(); String parent=null; String condition=null; if(cells.length>IDX_CONT_PARENT) { parent=cells[IDX_CONT_PARENT].getContents(); if(cells.length<=IDX_CONT_CONDITION) { throw new SpreadsheetLoadException(ctx, "Parent specified but without inheritance condition on container"); } condition = cells[IDX_CONT_CONDITION].getContents(); parents.put(name, parent); } if("".equals(parent)) { parent=null; } // absoluteoffset is the absolute offset of the first parameter of the container int absoluteoffset=-1; if(parent==null) { absoluteoffset=0; } else { int x=parent.indexOf(":"); if(x!=-1) { absoluteoffset=Integer.decode(parent.substring(x+1)); parent=parent.substring(0, x); } } int containerSizeInBits=-1; if(hasColumn(cells, IDX_CONT_SIZEINBITS)) { containerSizeInBits=Integer.decode(cells[IDX_CONT_SIZEINBITS].getContents()); } RateInStream rate=null; if(hasColumn(cells, IDX_CONT_EXPECTED_INTERVAL)) { int expint=Integer.decode(cells[IDX_CONT_EXPECTED_INTERVAL].getContents()); rate=new RateInStream(expint); } String description=""; if(hasColumn(cells, IDX_CONT_DESCRIPTION)) { description = cells[IDX_CONT_DESCRIPTION].getContents(); } // create a new SequenceContainer that will hold the parameters (i.e. SequenceEntries) for the ORDINARY/SUB/AGGREGATE packets, and register that new SequenceContainer in the containers hashmap SequenceContainer container = new SequenceContainer(name); container.sizeInBits=containerSizeInBits; containers.put(name, container); container.setRateInStream(rate); if( !description.isEmpty()) { container.setShortDescription(description); container.setLongDescription(description); } if(hasColumn(cells, IDX_CONT_FLAGS)) { String flags = cells[IDX_CONT_FLAGS].getContents(); if(flags.contains("a")) { container.useAsArchivePartition(true); } } XtceAliasSet xas = getAliases(firstRow, cells); if(xas!=null) { container.setAliasSet(xas); } //System.out.println("for "+name+" got absoluteOffset="+) // we mark the start of the command and advance to the next line, to get to the first argument (if there is one) int start = i++; // now, we start processing the parameters (or references to aggregate containers) boolean end = false; int counter = 0; // sequence number of the SequenceEntrys in the SequenceContainer while (!end && (i < sheet.getRows())) { // get the next row, containing a measurement/aggregate reference cells = jumpToRow(sheet, i); // determine whether we have not reached the end of the packet definition. if (!hasColumn(cells, IDX_CONT_PARA_NAME)) { end = true; continue; } // extract a few variables, for further use String flags = cells[IDX_CONT_FLAGS].getContents(); String paraname = cells[IDX_CONT_PARA_NAME].getContents(); int relpos = 0; if (hasColumn(cells, IDX_CONT_RELPOS)) { relpos = Integer.decode(cells[IDX_CONT_RELPOS].getContents()); } // we add the relative position to the absoluteOffset, to specify the location of the new parameter. // We only do this if the absoluteOffset is not equal to -1, // because that would mean that we cannot and should not use absolute positions anymore if (absoluteoffset != -1) { absoluteoffset += relpos; } // the repeat string will contain the number of times a measurement (or aggregate container) should be repeated. It is a String because at this point it can be either a number or a reference to another measurement String repeat = ""; // we check whether the measurement (or aggregate container) has a '*' inside it, meaning that it is a repeat measurement/aggregate Matcher m = Pattern.compile("(.*)[*](.*)").matcher(paraname); if (m.matches()) { repeat = m.group(1); paraname = m.group(2); } // check whether this measurement/aggregate actually has an entry in the parameters table // first we check if it is a measurement by trying to retrieve it from the parameters map. If this succeeds we add it as a new parameterentry, // otherwise, we search for it in the containersmap, as it is probably an aggregate. If it is not, we encountered an error // note that one of the next 2 lines will return null, but this does not pose a problem, it makes programming easier along the way Parameter param = parameters.get(paraname); SequenceContainer sc = containers.get(paraname); // if the sequenceentry is repeated a fixed number of times, this number is recorded in the 'repeated' variable and used to calculate the next absoluteoffset (done below) int repeated = -1; if (param != null) { SequenceEntry se; if(flags.contains("L") || flags.contains("l")) { if(param.parameterType instanceof IntegerParameterType) { ((IntegerParameterType)param.parameterType).encoding.byteOrder=ByteOrder.LITTLE_ENDIAN; } else if(param.parameterType instanceof FloatParameterType) { ((FloatParameterType)param.parameterType).encoding.byteOrder=ByteOrder.LITTLE_ENDIAN; } else if(param.parameterType instanceof EnumeratedParameterType) { ((EnumeratedParameterType)param.parameterType).encoding.byteOrder=ByteOrder.LITTLE_ENDIAN; } else { throw new SpreadsheetLoadException(ctx, "Little endian not supported for parameter "+param); } } // if absoluteoffset is -1, somewhere along the line we came across a measurement or aggregate that had as a result that the absoluteoffset could not be determined anymore; hence, a relative position is added if (absoluteoffset == -1) { se = new ParameterEntry(counter, container, relpos, ReferenceLocationType.previousEntry, param); } else { se = new ParameterEntry(counter, container, absoluteoffset, ReferenceLocationType.containerStart, param); } // also check if the parameter should be added multiple times, and if so, do so repeated = addRepeat(se, repeat); container.entryList.add(se); } else { if (sc != null) { // ok, we found it as an aggregate, so we add it to the entryList of container, as a new ContainerEntry // if absoluteoffset is -1, somewhere along the line we came across a measurement or aggregate that had as a result that the absoluteoffset could not be determined anymore; hence, a relative position is added SequenceEntry se; if (absoluteoffset == -1) { se = new ContainerEntry(counter, container, relpos, ReferenceLocationType.previousEntry, sc); } else { se = new ContainerEntry(counter, container, absoluteoffset, ReferenceLocationType.containerStart, sc); } // also check if the parameter should be added multiple times, and if so, do so repeated = addRepeat(se, repeat); container.entryList.add(se); } else { throw new SpreadsheetLoadException(ctx, "The measurement/container '" + paraname + "' was not found in the parameters or containers map"); } } // after adding this measurement, we need to update the absoluteoffset for the next one. For this, we add the size of the current SequenceEntry to the absoluteoffset int size=getSize(param,sc); if ((repeated != -1) && (size != -1) && (absoluteoffset != -1)) { absoluteoffset += repeated * size; } else { // from this moment on, absoluteoffset is set to -1, meaning that all subsequent SequenceEntries must be relative absoluteoffset = -1; } // increment the counters; i++; counter++; } // at this point, we have added all the parameters and aggregate containers to the current packets. What remains to be done is link it with its base if(parent!=null) { parents.put(name, parent); // the condition is parsed and used to create the container.restrictionCriteria //1) get the parent, from the same sheet SequenceContainer sc = containers.get(parent); //the parent is not in the same sheet, try to get from the Xtcedb if(sc==null) { sc = spaceSystem.getSequenceContainer(parent); } if (sc != null) { container.setBaseContainer(sc); if(("5.2".compareTo(fileFormatVersion) > 0) && (!parents.containsKey(parent))) { //prior to version 5.2 of the format, the second level of containers were used as archive partitions //TODO: remove when switching to 6.x format container.useAsArchivePartition(true); } } else { final SequenceContainer c=container; NameReference nr=new NameReference(parent, Type.SEQUENCE_CONTAINTER, new ResolvedAction() { @Override public boolean resolved(NameDescription nd) { SequenceContainer sc =(SequenceContainer) nd; c.setBaseContainer(sc); if("5.2".compareTo(fileFormatVersion) > 0) { if(sc.getBaseContainer()==null) { c.useAsArchivePartition(true); } } return true; } }); spaceSystem.addUnresolvedReference(nr); } // 2) extract the condition and create the restrictioncriteria if(!"".equals(condition.trim())) { container.restrictionCriteria=toMatchCriteria(spaceSystem, condition); MatchCriteria.printParsedMatchCriteria(log, container.restrictionCriteria, ""); } } else { if(spaceSystem.getRootSequenceContainer()==null) { spaceSystem.setRootSequenceContainer(container); } } spaceSystem.addSequenceContainer(container); } } protected void loadCommandSheet(SpaceSystem spaceSystem, String sheetName) { Sheet sheet = switchToSheet(sheetName, false); if(sheet==null) { return; } Cell[] firstRow = jumpToRow(sheet, 0); HashMap<String, MetaCommand> commands = new HashMap<>(); for (int i = 1; i < sheet.getRows(); i++) { // search for a new command definition, starting from row i // (explanatory note, i is incremented inside this loop too, and that's why the following 4 lines work) Cell[] cells = jumpToRow(sheet, i); if (cells == null || cells.length<1) { log.trace("Ignoring line {} because it's empty", ctx.row); continue; } if(cells[0].getContents().equals("")|| cells[0].getContents().startsWith("#")) { log.trace("Ignoring line {} because first cell is empty or starts with '#'", ctx.row); continue; } String name = cells[IDX_CMD_NAME].getContents(); String parent=null; String argAssignment=null; if(cells.length>IDX_CMD_PARENT) { parent=cells[IDX_CMD_PARENT].getContents(); if(hasColumn(cells, IDX_CMD_ARG_ASSIGNMENT)) { argAssignment = cells[IDX_CMD_ARG_ASSIGNMENT].getContents(); } } if("".equals(parent)) { parent=null; } // absoluteOffset is the absolute offset of the first argument or FixedValue in the container int absoluteOffset=-1; if(parent==null) { absoluteOffset=0; } else { int x=parent.indexOf(":"); if(x!=-1) { absoluteOffset=Integer.decode(parent.substring(x+1)); parent=parent.substring(0, x); } } // create a new SequenceContainer that will hold the parameters (i.e. SequenceEntries) for the ORDINARY/SUB/AGGREGATE packets, //and register that new SequenceContainer in the containers hashmap MetaCommandContainer container = new MetaCommandContainer(name); MetaCommand cmd = new MetaCommand(name); cmd.setMetaCommandContainer(container); commands.put(name, cmd); // load aliases XtceAliasSet xas = getAliases(firstRow, cells); if(xas!=null) { cmd.setAliasSet(xas); } if(hasColumn(cells, IDX_CMD_FLAGS)) { String flags = cells[IDX_CMD_FLAGS].getContents(); if(flags.contains("A")){ cmd.setAbstract(true); } } if(hasColumn(cells, IDX_CMD_DESCRIPTION)) { String shortDescription = cells[IDX_CMD_DESCRIPTION].getContents(); cmd.setShortDescription(shortDescription); } // we mark the start of the CMD and advance to the next line, to get to the first argument (if there is one) int start = i++; // now, we start processing the arguments boolean end = false; int counter = 0; // sequence number of the SequenceEntrys in the SequenceContainer while (!end && (i < sheet.getRows())) { // get the next row, containing a measurement/aggregate reference cells = jumpToRow(sheet, i); // determine whether we have not reached the end of the command definition. if (!hasColumn(cells, IDX_CMD_ARGNAME)) { end = true; continue; } String argname = cells[IDX_CMD_ARGNAME].getContents(); int relpos = 0; if (hasColumn(cells, IDX_CMD_RELPOS)) { relpos = Integer.decode(cells[IDX_CMD_RELPOS].getContents()); } if(!hasColumn(cells, IDX_CMD_ENGTYPE)) { throw new SpreadsheetLoadException(ctx, "engtype is not specified for "+argname+" on line "+(i+1)); } String engType = cells[IDX_CMD_ENGTYPE].getContents(); // we add the relative position to the absoluteOffset, to specify the location of the new parameter. // We only do this if the absoluteOffset is not equal to -1, because that would mean that we cannot and should not use absolute positions anymore if (absoluteOffset != -1) { absoluteOffset += relpos; } if(engType.equalsIgnoreCase("FixedValue")) { if(!hasColumn(cells, IDX_CMD_DEFVALUE)) { throw new SpreadsheetLoadException(ctx, "default value is not specified for "+argname+" which is a FixedValue on line "+(i+1)); } String hexValue = cells[IDX_CMD_DEFVALUE].getContents(); byte[] binaryValue = StringConverter.hexStringToArray(hexValue); if(!hasColumn(cells, IDX_CMD_SIZEINBITS)) { throw new SpreadsheetLoadException(ctx, "sizeInBits is not specified for "+argname+" which is a FixedValue on line "+(i+1)); } int sizeInBits = Integer.parseInt(cells[IDX_CMD_SIZEINBITS].getContents()); FixedValueEntry fve; if (absoluteOffset == -1) { fve = new FixedValueEntry(counter, container, relpos, ReferenceLocationType.previousEntry, argname, binaryValue, sizeInBits); } else { fve = new FixedValueEntry(counter, container, absoluteOffset, ReferenceLocationType.containerStart, argname, binaryValue, sizeInBits); } absoluteOffset += sizeInBits; container.entryList.add(fve); } else { absoluteOffset = loadArgument(cells, cmd, container, absoluteOffset, counter); } i++; counter++; } // at this point, we have added all the parameters and aggregate containers to the current packets. What remains to be done is link it with its base if(parent!=null) { // the condition is parsed and used to create the container.restrictionCriteria //1) get the parent, from the same sheet MetaCommand parentCmd = commands.get(parent); //the parent is not in the same sheet, try to get from the Xtcedb if(parentCmd==null) { parentCmd = spaceSystem.getMetaCommand(parent); } if (parentCmd != null) { cmd.setBaseMetaCommand(parentCmd); container.setBaseContainer(parentCmd.getCommandContainer()); } else { final MetaCommand mc = cmd; final MetaCommandContainer mcc = container; NameReference nr=new NameReference(parent, Type.META_COMMAND, new ResolvedAction() { @Override public boolean resolved(NameDescription nd) { mc.setBaseMetaCommand((MetaCommand) nd); mcc.setBaseContainer(((MetaCommand) nd).getCommandContainer()); return true; } }); spaceSystem.addUnresolvedReference(nr); } // 2) extract the condition and create the restrictioncriteria if(argAssignment!=null) { cmd.argumentAssignmentList=toArgumentAssignmentList(argAssignment); } } spaceSystem.addMetaCommand(cmd); } } protected void loadCommandOptionsSheet(SpaceSystem spaceSystem, String sheetName) { Sheet sheet = switchToSheet(sheetName, false); if(sheet==null) { return; } int i = 1; while(i<sheet.getRows()) { // search for a new command definition, starting from row i // (explanatory note, i is incremented inside this loop too, and that's why the following 4 lines work) Cell[] cells = jumpToRow(sheet, i); if (cells == null || cells.length<1) { log.trace("Ignoring line {} because it's empty", ctx.row); i++; continue; } if(cells[0].getContents().equals("")|| cells[0].getContents().startsWith("#")) { log.trace("Ignoring line {} because first cell is empty or starts with '#'", ctx.row); i++; continue; } String cmdName = cells[IDX_CMDOPT_NAME].getContents(); MetaCommand cmd = spaceSystem.getMetaCommand(cmdName); if(cmd == null) { throw new SpreadsheetLoadException(ctx, "Could not find a command named "+cmdName); } int cmdEnd = i + 1; while (cmdEnd < sheet.getRows()) { cells = jumpToRow(sheet, cmdEnd); if (hasColumn(cells, IDX_CMDOPT_NAME)) { break; } cmdEnd++; } while (i<cmdEnd) { cells = jumpToRow(sheet, i); if(hasColumn(cells, IDX_CMDOPT_TXCONST)) { String condition = cells[IDX_CMDOPT_TXCONST].getContents(); MatchCriteria criteria; try { criteria=toMatchCriteria(spaceSystem, condition); } catch (Exception e) { throw new SpreadsheetLoadException(ctx, "Cannot parse condition '"+condition+"': "+e); } long timeout = 0; if(hasColumn(cells, IDX_CMDOPT_TXCONST_TIMEOUT)) { timeout = Long.parseLong(cells[IDX_CMDOPT_TXCONST_TIMEOUT].getContents()); } TransmissionConstraint constraint = new TransmissionConstraint(criteria, timeout); cmd.addTransmissionConstrain(constraint); } if(hasColumn(cells, IDX_CMDOPT_SIGNIFICANCE)) { if(cmd.getDefaultSignificance()!=null) { throw new SpreadsheetLoadException(ctx, "The command "+cmd.getName()+ " has already a default significance"); } String significance = cells[IDX_CMDOPT_SIGNIFICANCE].getContents(); Significance.Levels slevel; try { slevel = Significance.Levels.valueOf(significance); } catch (IllegalArgumentException e) { throw new SpreadsheetLoadException(ctx, "Invalid significance '"+significance+"' specified. Available values are: "+Arrays.toString(Significance.Levels.values())); } String reason = null; if(hasColumn(cells, IDX_CMDOPT_SIGNIFICANCE_REASON)) { reason = cells[IDX_CMDOPT_SIGNIFICANCE_REASON].getContents(); } cmd.setDefaultSignificance(new Significance(slevel, reason)); } i++; } } } protected void loadCommandVerificationSheet(SpaceSystem spaceSystem, String sheetName) { Sheet sheet = switchToSheet(sheetName, false); if(sheet==null) { return; } int i = 1; while(i<sheet.getRows()) { // search for a new command definition, starting from row i // (explanatory note, i is incremented inside this loop too, and that's why the following 4 lines work) Cell[] cells = jumpToRow(sheet, i); if (cells == null || cells.length<1) { log.trace("Ignoring line {} because it's empty", ctx.row); i++; continue; } if(cells[0].getContents().equals("")|| cells[0].getContents().startsWith("#")) { log.trace("Ignoring line {} because first cell is empty or starts with '#'", ctx.row); i++; continue; } String cmdName = cells[IDX_CMDVERIF_NAME].getContents(); MetaCommand cmd = spaceSystem.getMetaCommand(cmdName); if(cmd == null) { throw new SpreadsheetLoadException(ctx, "Could not find a command named "+cmdName); } int cmdEnd = i + 1; while (cmdEnd < sheet.getRows()) { cells = jumpToRow(sheet, cmdEnd); if (hasColumn(cells, IDX_CMDVERIF_NAME)) { break; } cmdEnd++; } while (i<cmdEnd) { cells = jumpToRow(sheet, i); if(hasColumn(cells, IDX_CMDVERIF_STAGE)) { String stage = cells[IDX_CMDVERIF_STAGE].getContents(); if(!hasColumn(cells, IDX_CMDVERIF_CHECKWINDOW)) { throw new SpreadsheetLoadException(ctx, "No checkwindow specified for the command verifier "); } String checkws = cells[IDX_CMDVERIF_CHECKWINDOW].getContents(); Pattern p = Pattern.compile("(\\d+),(\\d+)"); Matcher m = p.matcher(checkws); if(!m.matches()) { throw new SpreadsheetLoadException(ctx, "Invalid checkwindow specified. Use 'start,stop' where start and stop are number of milliseconds. Both have to be positive."); } long start = Long.valueOf(m.group(1)); long stop = Long.valueOf(m.group(2)); if(stop<start) { throw new SpreadsheetLoadException(ctx, "Invalid checkwindow specified. Stop cannot be smaller than start"); } CheckWindow.TimeWindowIsRelativeToType cwr = TimeWindowIsRelativeToType.timeLastVerifierPassed; if(hasColumn(cells, IDX_CMDVERIF_CHECKWINDOW_RELATIVETO)) { String s = cells[IDX_CMDVERIF_CHECKWINDOW_RELATIVETO].getContents(); try { cwr = TimeWindowIsRelativeToType.valueOf(s); } catch (IllegalArgumentException e) { throw new SpreadsheetLoadException(ctx, "Invalid value '"+s+"' specified for CheckWindow relative to parameter. Use one of "+TimeWindowIsRelativeToType.values()); } } CheckWindow cw = new CheckWindow(start, stop, cwr); if(!hasColumn(cells, IDX_CMDVERIF_TYPE)) { throw new SpreadsheetLoadException(ctx, "No type specified for the command verifier "); } String types = cells[IDX_CMDVERIF_TYPE].getContents(); CommandVerifier.Type type = null; try { type = CommandVerifier.Type.valueOf(types); } catch (IllegalArgumentException e) { throw new SpreadsheetLoadException(ctx, "Invalid command verifier type '"+types+"' specified. Supported are: "+ Arrays.toString(CommandVerifier.Type.values())); } CommandVerifier cmdVerifier = new CommandVerifier(type, stage, cw); if(type==CommandVerifier.Type.container) { String containerName = cells[IDX_CMDVERIF_TEXT].getContents(); SequenceContainer container = spaceSystem.getSequenceContainer(containerName); if(container==null) { throw new SpreadsheetLoadException(ctx, "Cannot find sequence container '"+containerName+"' required for the verifier"); } cmdVerifier.setContainerRef(container); } else if(type==CommandVerifier.Type.algorithm) { String algoName = cells[IDX_CMDVERIF_TEXT].getContents(); Algorithm algo = spaceSystem.getAlgorithm(algoName); if(algo==null) { throw new SpreadsheetLoadException(ctx, "Cannot find algorithm '"+algoName+"' required for the verifier"); } cmdVerifier.setAlgorithm(algo); } else { throw new SpreadsheetLoadException(ctx, "Command verifier type '"+type+"' not implemented "); } String tas = null; try { if(hasColumn(cells, IDX_CMDVERIF_ONSUCCESS)) { tas = cells[IDX_CMDVERIF_ONSUCCESS].getContents(); CommandVerifier.TerminationAction ta = TerminationAction.valueOf(tas); cmdVerifier.setOnSuccess(ta); } if(hasColumn(cells, IDX_CMDVERIF_ONFAIL)) { tas = cells[IDX_CMDVERIF_ONFAIL].getContents(); CommandVerifier.TerminationAction ta = TerminationAction.valueOf(tas); cmdVerifier.setOnFail(ta); } if(hasColumn(cells, IDX_CMDVERIF_ONTIMEOUT)) { tas = cells[IDX_CMDVERIF_ONTIMEOUT].getContents(); CommandVerifier.TerminationAction ta = TerminationAction.valueOf(tas); cmdVerifier.setOnTimeout(ta); } cmd.addVerifier(cmdVerifier); } catch (IllegalArgumentException e) { throw new SpreadsheetLoadException(ctx, "Invalid termination action '"+tas+"' specified for the command verifier. Supported actions are: "+TerminationAction.values()); } } i++; } } } private List<ArgumentAssignment> toArgumentAssignmentList(String argAssignment) { List<ArgumentAssignment> aal = new ArrayList<ArgumentAssignment>(); String splitted[] = argAssignment.split(";"); for (String part: splitted) { aal.add(toArgumentAssignment(part)); } return aal; } private ArgumentAssignment toArgumentAssignment(String argAssignment) { Matcher m = Pattern.compile("(.*?)(=)(.*)").matcher(argAssignment); if(!m.matches()) { throw new SpreadsheetLoadException(ctx, "Cannot parse argument assignment '"+argAssignment+"'"); } String aname=m.group(1).trim(); String value=m.group(3).trim(); return new ArgumentAssignment(aname, value); } private int loadArgument(Cell[] cells, MetaCommand cmd, MetaCommandContainer container, int absoluteOffset, int counter) { String engType = cells[IDX_CMD_ENGTYPE].getContents(); String name = cells[IDX_CMD_ARGNAME].getContents(); int relpos = hasColumn(cells, IDX_CMD_RELPOS)?Integer.decode(cells[IDX_CMD_RELPOS].getContents()):0; String calib = null; if(hasColumn(cells, IDX_CMD_CALIBRATION)) { calib = cells[IDX_CMD_CALIBRATION].getContents(); } String flags = null; if(hasColumn(cells, IDX_CMD_FLAGS)) { flags = cells[IDX_CMD_FLAGS].getContents(); } int sizeInBits = -1; if(hasColumn(cells, IDX_CMD_SIZEINBITS)) { sizeInBits = Integer.parseInt(cells[IDX_CMD_SIZEINBITS].getContents()); } String rawType = engType; if(hasColumn(cells, IDX_CMD_RAWTYPE)) { rawType = cells[IDX_CMD_RAWTYPE].getContents(); } if("n".equals(calib) || "".equals(calib)){ calib = null; } else if("y".equalsIgnoreCase(calib)) { calib = name; } ArgumentType atype=null; if ("uint".equalsIgnoreCase(engType)) { atype = new IntegerArgumentType(name); ((IntegerArgumentType)atype).signed = false; } else if ("uint64".equalsIgnoreCase(engType)) { atype = new IntegerArgumentType(name); ((IntegerArgumentType)atype).signed = false; ((IntegerArgumentType)atype).setSizeInBits(64); } else if ("int".equalsIgnoreCase(engType)) { atype = new IntegerArgumentType(name); } else if("int64".equalsIgnoreCase(engType)) { atype = new IntegerArgumentType(name); ((IntegerArgumentType)atype).setSizeInBits(64); } else if ("float".equalsIgnoreCase(engType)) { atype = new FloatArgumentType(name); } else if ("double".equalsIgnoreCase(engType)) { atype = new FloatArgumentType(name); ((FloatArgumentType)atype).setSizeInBits(64); } else if ("enumerated".equalsIgnoreCase(engType)) { if(calib==null) { throw new SpreadsheetLoadException(ctx, "Argument " + name + " has to have an enumeration"); } EnumerationDefinition enumeration = enumerations.get(calib); if (enumeration == null) { throw new SpreadsheetLoadException(ctx, "Argument " + name + " is supposed to have an enumeration '" + calib + "' but the enumeration does not exist"); } atype = new EnumeratedArgumentType(calib); for (Entry<Long,String> entry:enumeration.valueMap.entrySet()) { ((EnumeratedArgumentType) atype).addEnumerationValue(entry.getKey(), entry.getValue()); } } else if ("string".equalsIgnoreCase(engType)) { atype = new StringArgumentType(name); } else if ("binary".equalsIgnoreCase(engType)) { atype = new BinaryArgumentType(name); } else if ("boolean".equalsIgnoreCase(engType)) { atype = new BooleanArgumentType(name); }else { throw new SpreadsheetLoadException(ctx, "Unknown argument type " + engType); } if(cmd.getArgument(name)!=null) { throw new SpreadsheetLoadException(ctx, "Duplicate argument with name '"+name+"'"); } Argument arg = new Argument(name); cmd.addArgument(arg); if(hasColumn(cells, IDX_CMD_DEFVALUE)) { String v = cells[IDX_CMD_DEFVALUE].getContents(); if(atype instanceof IntegerArgumentType) { try { Long.decode(v); } catch(Exception e) { throw new SpreadsheetLoadException(ctx, "Cannot parse default value '"+v+"'"); } arg.setInitialValue(v); } else if (atype instanceof FloatArgumentType) { try { Double.parseDouble(v); } catch(Exception e) { throw new SpreadsheetLoadException(ctx, "Cannot parse default value '"+v+"'"); } arg.setInitialValue(v); } else { arg.setInitialValue(v); } } if(hasColumn(cells, IDX_CMD_RANGELOW) || hasColumn(cells, IDX_CMD_RANGEHIGH)) { if(atype instanceof IntegerArgumentType) { if(((IntegerArgumentType) atype).isSigned()) { long minInclusive = Long.MIN_VALUE; long maxInclusive = Long.MAX_VALUE; if(hasColumn(cells, IDX_CMD_RANGELOW)) { minInclusive = Long.decode(cells[IDX_CMD_RANGELOW].getContents()); } if(hasColumn(cells, IDX_CMD_RANGEHIGH)) { maxInclusive = Long.decode(cells[IDX_CMD_RANGEHIGH].getContents()); } IntegerValidRange range = new IntegerValidRange(minInclusive, maxInclusive); ((IntegerArgumentType)atype).setValidRange(range); } else { long minInclusive = 0; long maxInclusive = ~0; if(hasColumn(cells, IDX_CMD_RANGELOW)) { minInclusive = UnsignedLongs.decode(cells[IDX_CMD_RANGELOW].getContents()); } if(hasColumn(cells, IDX_CMD_RANGEHIGH)) { maxInclusive = UnsignedLongs.decode(cells[IDX_CMD_RANGEHIGH].getContents()); } IntegerValidRange range = new IntegerValidRange(minInclusive, maxInclusive); ((IntegerArgumentType)atype).setValidRange(range); } } else if(atype instanceof FloatArgumentType) { double minInclusive = Double.NEGATIVE_INFINITY; double maxInclusive = Double.POSITIVE_INFINITY; if(hasColumn(cells, IDX_CMD_RANGELOW)) { minInclusive = Double.parseDouble(cells[IDX_CMD_RANGELOW].getContents()); } if(hasColumn(cells, IDX_CMD_RANGEHIGH)) { maxInclusive = Double.parseDouble(cells[IDX_CMD_RANGEHIGH].getContents()); } FloatValidRange range = new FloatValidRange(minInclusive, maxInclusive); ((FloatArgumentType)atype).setValidRange(range); } } if(hasColumn(cells, IDX_CMD_DESCRIPTION)) { String shortDescription = cells[IDX_CMD_DESCRIPTION].getContents(); arg.setShortDescription(shortDescription); } ArgumentEntry ae; // if absoluteoffset is -1, somewhere along the line we came across a measurement or aggregate that had as a result that the absoluteoffset could not be determined anymore; hence, a relative position is added if (absoluteOffset == -1) { ae = new ArgumentEntry(counter, container, relpos, ReferenceLocationType.previousEntry, arg); } else { ae = new ArgumentEntry(counter, container, absoluteOffset, ReferenceLocationType.containerStart, arg); } container.entryList.add(ae); ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; if((flags!=null) && (flags.contains("L") || flags.contains("l"))) { byteOrder = ByteOrder.LITTLE_ENDIAN; } // after adding this argument, we need to update the absoluteOffset for the next one. // For this, we add the size of the current ArgumentEntry to the absoluteOffset if ((sizeInBits != -1) && (absoluteOffset != -1)) { absoluteOffset += sizeInBits; } else { // from this moment on, absoluteOffset is set to -1, meaning that all subsequent SequenceEntries must be relative absoluteOffset = -1; } String units=null; if(hasColumn(cells, IDX_CMD_ENGUNIT)) { units = cells[IDX_CMD_ENGUNIT].getContents(); if(!"".equals(units) && units != null && atype instanceof BaseDataType) { UnitType unitType = new UnitType(units); ((BaseDataType) atype).addUnit(unitType); } } //loadParameterLimits(ptype,cells); //calibrations DataEncoding encoding = null; if (("uint".equalsIgnoreCase(rawType)) || rawType.toLowerCase().startsWith("int")) { if(sizeInBits==-1) { throw new SpreadsheetLoadException(ctx, "Size in bits length is mandatory for integer arguments"); } encoding = new IntegerDataEncoding(sizeInBits, byteOrder); if (rawType.toLowerCase().startsWith("int")) { if ("int".equals(rawType)) { ((IntegerDataEncoding)encoding).encoding = IntegerDataEncoding.Encoding.twosComplement; } else { int startBracket = rawType.indexOf('('); if (startBracket != -1) { int endBracket = rawType.indexOf(')', startBracket); if (endBracket != -1) { String intRepresentation = rawType.substring(startBracket+1, endBracket).trim().toLowerCase(); if ("2c".equals(intRepresentation)) { ((IntegerDataEncoding)encoding).encoding = IntegerDataEncoding.Encoding.twosComplement; } else if ("si".equals(intRepresentation)) { ((IntegerDataEncoding)encoding).encoding = IntegerDataEncoding.Encoding.signMagnitude; } else { throw new SpreadsheetLoadException(ctx, "Unsupported signed integer representation: "+intRepresentation); } } } } } if ((!"enumerated".equalsIgnoreCase(engType)) && (calib!=null)) { Calibrator c = calibrators.get(calib); if (c == null) { throw new SpreadsheetLoadException(ctx, "Argument " + name + " is supposed to have a calibrator '" + calib + "' but the calibrator does not exist"); } ((IntegerDataEncoding)encoding).defaultCalibrator = c; } } else if ("binary".equalsIgnoreCase(rawType) || "bytestream".equalsIgnoreCase(rawType)) { if(sizeInBits==-1) { throw new SpreadsheetLoadException(ctx, "Bit length is mandatory for binary parameters"); } encoding=new BinaryDataEncoding(name, sizeInBits); } else if ("boolean".equalsIgnoreCase(rawType)) { if(sizeInBits!=-1) { throw new SpreadsheetLoadException(ctx, "Bit length is not allowed for boolean parameters (defaults to 1). Use any other raw type if you want to specify the bitlength"); } encoding = new BooleanDataEncoding(); } else if ("string".equalsIgnoreCase(rawType)) { // Version <= 1.6 String type // STRING if(sizeInBits==-1) { // Assume null-terminated if no length specified encoding=new StringDataEncoding(name, StringDataEncoding.SizeType.TerminationChar); } else { encoding=new StringDataEncoding(name, StringDataEncoding.SizeType.Fixed); encoding.setSizeInBits(sizeInBits); } } else if ( "fixedstring".equalsIgnoreCase( rawType ) ) { // v1.7 String type // FIXEDSTRING if(sizeInBits==-1) { throw new SpreadsheetLoadException(ctx, "SizeInBits is mandatory for fixedstring raw type"); } encoding=new StringDataEncoding(name, StringDataEncoding.SizeType.Fixed); if((sizeInBits&0x7)!=0) { throw new SpreadsheetLoadException(ctx, "SizeInBits has to be multiple of 8 for fixedstring raw type"); } encoding.setSizeInBits(sizeInBits); } else if ( rawType.toLowerCase().startsWith( "terminatedstring" ) ) { // v1.7 String type // TERMINATEDSTRING encoding=new StringDataEncoding(name, StringDataEncoding.SizeType.TerminationChar); encoding.setSizeInBits(sizeInBits); // Use specified byte if found, otherwise accept class default. int startBracket = rawType.indexOf( '(' ); if( startBracket != -1 ) { int endBracket = rawType.indexOf( ')', startBracket ); if( endBracket != -1 ) { try { byte terminationChar = Byte.parseByte(rawType.substring( rawType.indexOf('x', startBracket)+1, endBracket ).trim(), 16 ); ((StringDataEncoding)encoding).setTerminationChar(terminationChar); } catch (NumberFormatException e) { throw new SpreadsheetLoadException(ctx, "Could not parse specified base 16 terminator from "+rawType); } } } } else if ( rawType.toLowerCase().startsWith( "prependedsizestring" ) ) { // v1.7 String type // PREPENDEDSIZESTRING encoding=new StringDataEncoding( name, StringDataEncoding.SizeType.LeadingSize ); encoding.setSizeInBits(sizeInBits); // Use specified size if found, otherwise accept class default. int startBracket = rawType.indexOf( '(' ); if( startBracket != -1 ) { int endBracket = rawType.indexOf( ')', startBracket ); if( endBracket != -1 ) { try { int sizeInBitsOfSizeTag = Integer.parseInt( rawType.substring(startBracket+1, endBracket).trim() ); ((StringDataEncoding)encoding).setSizeInBitsOfSizeTag( sizeInBitsOfSizeTag ); } catch (NumberFormatException e) { throw new SpreadsheetLoadException(ctx, "Could not parse integer size from "+rawType); } } } } else if ("float".equalsIgnoreCase(rawType) || "double".equalsIgnoreCase(rawType)) { if(sizeInBits==-1) { throw new SpreadsheetLoadException(ctx, "Size in bits is mandatory for integer arguments"); } encoding=new FloatDataEncoding(sizeInBits, byteOrder); if((!"enumerated".equalsIgnoreCase(engType)) && (calib!=null)) { Calibrator c = calibrators.get(calib); if (c == null) { throw new SpreadsheetLoadException(ctx, "Parameter " + name + " is supposed to have a calibrator '" + calib + "' but the calibrator does not exist."); } else { ((FloatDataEncoding)encoding).defaultCalibrator = c; } } } else { throw new SpreadsheetLoadException(ctx, "Unknown raw type " + rawType); } if (atype instanceof IntegerArgumentType) { // Integers can be encoded as strings if( encoding instanceof StringDataEncoding ) { // Create a new int encoding which uses the configured string encoding IntegerDataEncoding intStringEncoding = new IntegerDataEncoding(name, ((StringDataEncoding)encoding)); if( calib != null ) { Calibrator c = calibrators.get(calib); if( c == null ) { throw new SpreadsheetLoadException(ctx, "Parameter " + name + " specified calibrator '" + calib + "' but the calibrator does not exist"); } intStringEncoding.defaultCalibrator = c; } ((IntegerArgumentType)atype).setEncoding(intStringEncoding); } else { ((IntegerArgumentType)atype).setEncoding(encoding); } } else if (atype instanceof BinaryArgumentType) { ((BinaryArgumentType)atype).encoding = encoding; } else if (atype instanceof FloatArgumentType) { // Floats can be encoded as strings if ( encoding instanceof StringDataEncoding ) { // Create a new float encoding which uses the configured string encoding FloatDataEncoding floatStringEncoding = new FloatDataEncoding(((StringDataEncoding)encoding)); if(calib!=null) { Calibrator c = calibrators.get(calib); if( c == null ) { throw new SpreadsheetLoadException(ctx, "Parameter " + name + " specified calibrator '" + calib + "' but the calibrator does not exist."); } else { floatStringEncoding.defaultCalibrator = c; } } floatStringEncoding.setByteOrder(byteOrder); ((FloatArgumentType)atype).setEncoding(floatStringEncoding); } else { ((FloatArgumentType)atype).setEncoding(encoding); } } else if (atype instanceof EnumeratedArgumentType) { if(((EnumeratedArgumentType) atype).getEncoding() != null) { // Some other param has already led to setting the encoding of this shared ptype. // Do some basic consistency checks if(((EnumeratedArgumentType) atype).getEncoding().getSizeInBits() != encoding.getSizeInBits()) { throw new SpreadsheetLoadException(ctx, "Multiple parameters are sharing calibrator '"+calib+"' with different bit sizes."); } } // Enumerations encoded as string integers if( encoding instanceof StringDataEncoding ) { IntegerDataEncoding intStringEncoding = new IntegerDataEncoding(name, ((StringDataEncoding)encoding)); // Don't set calibrator, already done when making ptype ((EnumeratedArgumentType) atype).setEncoding(intStringEncoding); intStringEncoding.setByteOrder(byteOrder); } else { ((EnumeratedArgumentType) atype).setEncoding(encoding); } } else if (atype instanceof StringArgumentType) { ((StringArgumentType)atype).setEncoding(encoding); } else if (atype instanceof BooleanArgumentType) { ((BooleanArgumentType)atype).setEncoding(encoding); } else { throw new RuntimeException("Don't know what to do with "+atype); } arg.setArgumentType(atype); return absoluteOffset; } protected void loadChangelogSheet(boolean required) { Sheet sheet = switchToSheet(SHEET_CHANGELOG, required); if(sheet==null) { return; } int i = 1; while(i<sheet.getRows()) { Cell[] cells = jumpToRow(sheet, i); if (cells == null || cells.length<1) { log.trace("Ignoring line {} because it's empty", ctx.row); i++; continue; } if(cells[0].getContents().equals("")|| cells[0].getContents().startsWith("#")) { log.trace("Ignoring line {} because first cell is empty or starts with '#'", ctx.row); i++; continue; } if (cells.length >= 2) { String version = cells[IDX_LOG_VERSION].getContents(); String date; Cell dateCell = cells[IDX_LOG_DATE]; if (dateCell.getType() == CellType.DATE) { Date dt = ((DateCell) dateCell).getDate(); date = new SimpleDateFormat("dd-MMM-YYYY").format(dt); } else { date = cells[IDX_LOG_DATE].getContents(); } String msg = null; if (cells.length >= 3) { msg = cells[IDX_LOG_MESSAGE].getContents(); } History history = new History(version, date, msg); rootSpaceSystem.getHeader().addHistory(history); } i++; } } protected void loadAlgorithmsSheet(SpaceSystem spaceSystem, String sheetName) { Sheet sheet = switchToSheet(sheetName, false); if (sheet == null) { return; } Cell[] firstRow = jumpToRow(sheet, 0); // start at 1 to not use the first line (= title line) int start = 1; while(true) { // we first search for a row containing (= starting) a new algorithm while (start < sheet.getRows()) { Cell[] cells = jumpToRow(sheet, start); if ((cells.length > 0) && (cells[0].getContents().length() > 0) && !cells[0].getContents().startsWith("#")) { break; } start++; } if (start >= sheet.getRows()) { break; } Cell[] cells = jumpToRow(sheet, start); String name = cells[IDX_ALGO_NAME].getContents(); String algorithmLanguage = cells[IDX_ALGO_LANGUGAGE].getContents(); if(!"JavaScript".equals(algorithmLanguage) && !"python".equals(algorithmLanguage)&& !"java".equalsIgnoreCase(algorithmLanguage)) { throw new SpreadsheetLoadException(ctx, "Invalid algorithm language '"+algorithmLanguage+"' specified. Supported are 'JavaScript', 'python' and java (case sensitive)"); } String algorithmText = cells[IDX_ALGO_TEXT].getContents(); XtceAliasSet xas = getAliases(firstRow, cells); //Check that there is not specified by mistake a in/out param already on the same line with the algorithm name if(hasColumn(cells, IDX_ALGO_PARA_INOUT) || hasColumn(cells, IDX_ALGO_PARA_REF)) { throw new SpreadsheetLoadException(ctx, "Algorithm paramters have to start on the next line from the algorithm name and text definition"); } // now we search for the matching last row of that algorithm int end = start + 1; while (end < sheet.getRows()) { cells = jumpToRow(sheet, end); if (!hasColumn(cells, IDX_ALGO_PARA_REF)) { break; } end++; } Algorithm algorithm = new Algorithm(name); if(xas!=null) { algorithm.setAliasSet(xas); } algorithm.setLanguage(algorithmLanguage); // Replace smart-quotes “ and ” with regular quotes " algorithm.setAlgorithmText(algorithmText.replaceAll("[\u201c\u201d]", "\"")); // In/out params String paraInout=null; Set<String> inputParameterRefs=new HashSet<String>(); for (int j = start+1; j < end; j++) { cells = jumpToRow(sheet, j); String paraRef = cells[IDX_ALGO_PARA_REF].getContents(); if(hasColumn(cells, IDX_ALGO_PARA_INOUT)) { paraInout=cells[IDX_ALGO_PARA_INOUT].getContents(); } String flags = hasColumn(cells, IDX_ALGO_PARA_FLAGS)?cells[IDX_ALGO_PARA_FLAGS].getContents():""; if(paraInout==null) { throw new SpreadsheetLoadException(ctx, "You must specify in/out attribute for this parameter"); } if ("in".equalsIgnoreCase(paraInout)) { if(paraRef.startsWith(XtceDb.YAMCS_CMD_SPACESYSTEM_NAME) || paraRef.startsWith(XtceDb.YAMCS_CMDHIST_SPACESYSTEM_NAME)) { algorithm.setScope(Algorithm.Scope.commandVerification); } inputParameterRefs.add(paraRef); Parameter param = spaceSystem.getParameter(paraRef); final ParameterInstanceRef parameterInstance = new ParameterInstanceRef(null); if(param==null) { NameReference nr=new NameReference(paraRef, Type.PARAMETER, new ResolvedAction() { @Override public boolean resolved(NameDescription nd) { parameterInstance.setParameter((Parameter) nd); return true; } }); spaceSystem.addUnresolvedReference(nr); } else { parameterInstance.setParameter(param); } if (cells.length > IDX_ALGO_PARA_INSTANCE) { if (!"".equals(cells[IDX_ALGO_PARA_INSTANCE].getContents())) { int instance = Integer.valueOf(cells[IDX_ALGO_PARA_INSTANCE].getContents()); if (instance > 0) { throw new SpreadsheetLoadException(ctx, "Instance '"+instance+"' not supported. Can only go back in time. Use values <= 0."); } parameterInstance.setInstance(instance); } } InputParameter inputParameter = new InputParameter(parameterInstance); if (hasColumn(cells, IDX_ALGO_PARA_NAME)) { inputParameter.setInputName(cells[IDX_ALGO_PARA_NAME].getContents()); } if(flags.contains("M")) { inputParameter.setMandatory(true); } algorithm.addInput(inputParameter); } else if ("out".equalsIgnoreCase(paraInout)) { Parameter param = spaceSystem.getParameter(paraRef); if (param == null) { throw new SpreadsheetLoadException(ctx, "The measurement '" + paraRef + "' was not found on the parameters sheet"); } outputParameters.add(param); OutputParameter outputParameter = new OutputParameter(param); if (hasColumn(cells, IDX_ALGO_PARA_NAME)) { outputParameter.setOutputName(cells[IDX_ALGO_PARA_NAME].getContents()); } algorithm.addOutput(outputParameter); } else { throw new SpreadsheetLoadException(ctx, "In/out '"+paraInout+"' not supported. Must be one of 'in' or 'out'"); } } // Add trigger conditions final TriggerSetType triggerSet = new TriggerSetType(); Pattern PARAMETER_PATTERN=Pattern.compile("OnParameterUpdate\\((.*)\\)"); Pattern FIRERATE_PATTERN=Pattern.compile("OnPeriodicRate\\((\\d+)\\)"); cells = jumpToRow(sheet, start); // Jump back to algorithm row (for getting error msgs right) String triggerText = hasColumn(cells, IDX_ALGO_TRIGGER) ? cells[IDX_ALGO_TRIGGER].getContents() : ""; if(triggerText.startsWith("OnParameterUpdate")) { Matcher matcher = PARAMETER_PATTERN.matcher(triggerText); if(matcher.matches()) { for(String s:matcher.group(1).split(",")) { Parameter para = spaceSystem.getParameter(s.trim()); if(para!=null) { OnParameterUpdateTrigger trigger = new OnParameterUpdateTrigger(para); triggerSet.addOnParameterUpdateTrigger(trigger); } else { NameReference nr=new NameReference(s.trim(), Type.PARAMETER, new ResolvedAction() { @Override public boolean resolved(NameDescription nd) { OnParameterUpdateTrigger trigger = new OnParameterUpdateTrigger((Parameter) nd); triggerSet.addOnParameterUpdateTrigger(trigger); return true; } }); spaceSystem.addUnresolvedReference(nr); } } } else { throw new SpreadsheetLoadException(ctx, "Wrongly formatted OnParameterUpdate trigger"); } } else if(triggerText.startsWith("OnPeriodicRate")) { Matcher matcher = FIRERATE_PATTERN.matcher(triggerText); if(matcher.matches()) { long fireRateMs = Long.parseLong(matcher.group(1), 10); OnPeriodicRateTrigger trigger=new OnPeriodicRateTrigger(fireRateMs); triggerSet.addOnPeriodicRateTrigger(trigger); } else { throw new SpreadsheetLoadException(ctx, "Wrongly formatted OnPeriodicRate trigger"); } } else if (triggerText.startsWith("OnInputParameterUpdate")) { // default to all in parameters for(String paraRef:inputParameterRefs) { Parameter para=spaceSystem.getParameter(paraRef); if(para!=null) { triggerSet.addOnParameterUpdateTrigger(new OnParameterUpdateTrigger(para)); } else { NameReference nr=new NameReference(paraRef, Type.PARAMETER, new ResolvedAction() { @Override public boolean resolved(NameDescription nd) { OnParameterUpdateTrigger trigger = new OnParameterUpdateTrigger((Parameter) nd); triggerSet.addOnParameterUpdateTrigger(trigger); return true; } }); spaceSystem.addUnresolvedReference(nr); } } } else if(triggerText.isEmpty() || triggerText.startsWith("none")) { //do nothing, we run with an empty trigger set } else { throw new SpreadsheetLoadException(ctx, "Trigger '"+triggerText+"' not supported."); } algorithm.setTriggerSet(triggerSet); spaceSystem.addAlgorithm(algorithm); start = end; } } protected void loadAlarmsSheet(SpaceSystem spaceSystem, String sheetName) { Sheet sheet = switchToSheet(sheetName, false); if (sheet == null) { return; } // start at 1 to not use the first line (= title line) int start = 1; while(true) { // we first search for a row containing (= starting) a new alarm while (start < sheet.getRows()) { Cell[] cells = jumpToRow(sheet, start); if ((cells.length > 0) && (cells[0].getContents().length() > 0) && !cells[0].getContents().startsWith("#")) { break; } start++; } if (start >= sheet.getRows()) { break; } Cell[] cells = jumpToRow(sheet, start); if(!hasColumn(cells, IDX_ALARM_PARAM_NAME)) { throw new SpreadsheetLoadException(ctx, "Alarms must be attached to a parameter name"); } String paramName = cells[IDX_ALARM_PARAM_NAME].getContents(); Parameter para = spaceSystem.getParameter(paramName); if(para == null) { throw new SpreadsheetLoadException(ctx, "Could not find a parameter named "+paramName); } // now we search for the matching last row of the alarms for this parameter int paramEnd = start + 1; while (paramEnd < sheet.getRows()) { cells = jumpToRow(sheet, paramEnd); if (hasColumn(cells, IDX_ALARM_PARAM_NAME)) { break; } paramEnd++; } // Iterate over all rows for this parameter MatchCriteria previousContext=null; int minViolations=-1; AlarmReportType reportType=AlarmReportType.ON_SEVERITY_CHANGE; for (int j = start; j < paramEnd; j++) { cells = jumpToRow(sheet, j); MatchCriteria context=previousContext; if(hasColumn(cells, IDX_ALARM_CONTEXT)) { String contextString = cells[IDX_ALARM_CONTEXT].getContents(); context=toMatchCriteria(spaceSystem, contextString); minViolations = -1; } if(hasColumn(cells, IDX_ALARM_MIN_VIOLATIONS)) { minViolations=Integer.parseInt(cells[IDX_ALARM_MIN_VIOLATIONS].getContents()); } if(hasColumn(cells, IDX_ALARM_REPORT)) { if("OnSeverityChange".equalsIgnoreCase(cells[IDX_ALARM_REPORT].getContents())) { reportType=AlarmReportType.ON_SEVERITY_CHANGE; } else if("OnValueChange".equalsIgnoreCase(cells[IDX_ALARM_REPORT].getContents())) { reportType=AlarmReportType.ON_VALUE_CHANGE; } else { throw new SpreadsheetLoadException(ctx, "Unrecognized report type '"+cells[IDX_ALARM_REPORT].getContents()+"'"); } } if(hasColumn(cells, IDX_ALARM_WATCH_TRIGGER) && hasColumn(cells, IDX_ALARM_WATCH_VALUE)) { String trigger=cells[IDX_ALARM_WATCH_TRIGGER].getContents(); String triggerValue=cells[IDX_ALARM_WATCH_VALUE].getContents(); addAlarmRange(para, context, trigger, triggerValue, AlarmLevels.watch); } if(hasColumn(cells, IDX_ALARM_WARNING_TRIGGER) && hasColumn(cells, IDX_ALARM_WARNING_VALUE)) { String trigger=cells[IDX_ALARM_WARNING_TRIGGER].getContents(); String triggerValue=cells[IDX_ALARM_WARNING_VALUE].getContents(); addAlarmRange(para, context, trigger, triggerValue, AlarmLevels.warning); } if(hasColumn(cells, IDX_ALARM_DISTRESS_TRIGGER) && hasColumn(cells, IDX_ALARM_DISTRESS_VALUE)) { String trigger=cells[IDX_ALARM_DISTRESS_TRIGGER].getContents(); String triggerValue=cells[IDX_ALARM_DISTRESS_VALUE].getContents(); addAlarmRange(para, context, trigger, triggerValue, AlarmLevels.distress); } if(hasColumn(cells, IDX_ALARM_CRITICAL_TRIGGER) && hasColumn(cells, IDX_ALARM_CRITICAL_VALUE)) { String trigger=cells[IDX_ALARM_CRITICAL_TRIGGER].getContents(); String triggerValue=cells[IDX_ALARM_CRITICAL_VALUE].getContents(); addAlarmRange(para, context, trigger, triggerValue, AlarmLevels.critical); } if(hasColumn(cells, IDX_ALARM_SEVERE_TRIGGER) && hasColumn(cells, IDX_ALARM_SEVERE_VALUE)) { String trigger=cells[IDX_ALARM_SEVERE_TRIGGER].getContents(); String triggerValue=cells[IDX_ALARM_SEVERE_VALUE].getContents(); addAlarmRange(para, context, trigger, triggerValue, AlarmLevels.severe); } // Set minviolations and alarmreporttype AlarmType alarm=null; if(para.getParameterType() instanceof IntegerParameterType) { IntegerParameterType ipt=(IntegerParameterType)para.getParameterType(); alarm=(context==null)?ipt.getDefaultAlarm():ipt.getNumericContextAlarm(context); if(reportType != AlarmType.DEFAULT_REPORT_TYPE) { ipt.createOrGetAlarm(context).setAlarmReportType(reportType); } } else if(para.getParameterType() instanceof FloatParameterType) { FloatParameterType fpt=(FloatParameterType)para.getParameterType(); alarm=(context==null)?fpt.getDefaultAlarm():fpt.getNumericContextAlarm(context); if(reportType != AlarmType.DEFAULT_REPORT_TYPE) { fpt.createOrGetAlarm(context).setAlarmReportType(reportType); } } else if(para.getParameterType() instanceof EnumeratedParameterType) { EnumeratedParameterType ept=(EnumeratedParameterType)para.getParameterType(); alarm=(context==null)?ept.getDefaultAlarm():ept.getContextAlarm(context); if(reportType != AlarmType.DEFAULT_REPORT_TYPE) { ept.createOrGetAlarm(context).setAlarmReportType(reportType); } } if(alarm!=null) { // It's possible that this gets called multiple times per alarm, but doesn't matter alarm.setMinViolations((minViolations==-1) ? 1 : minViolations); alarm.setAlarmReportType(reportType); } previousContext=context; } start = paramEnd; } } private void addAlarmRange(Parameter para, MatchCriteria context, String trigger, String triggerValue, AlarmLevels level) { if(para.getParameterType() instanceof IntegerParameterType) { IntegerParameterType ipt=(IntegerParameterType)para.getParameterType(); if("low".equals(trigger)) { ipt.addAlarmRange(context, new FloatRange(Double.parseDouble(triggerValue),Double.POSITIVE_INFINITY), level); } else if("high".equals(trigger)) { ipt.addAlarmRange(context, new FloatRange(Double.NEGATIVE_INFINITY, Double.parseDouble(triggerValue)), level); } else { throw new SpreadsheetLoadException(ctx, "Unexpected trigger type '"+trigger+"' for numeric parameter "+para.getName()); } } else if(para.getParameterType() instanceof FloatParameterType) { FloatParameterType fpt=(FloatParameterType)para.getParameterType(); if("low".equals(trigger)) { fpt.addAlarmRange(context, new FloatRange(Double.parseDouble(triggerValue),Double.POSITIVE_INFINITY), level); } else if("high".equals(trigger)) { fpt.addAlarmRange(context, new FloatRange(Double.NEGATIVE_INFINITY, Double.parseDouble(triggerValue)), level); } else { throw new SpreadsheetLoadException(ctx, "Unexpected trigger type '"+trigger+"' for numeric parameter "+para.getName()); } } else if(para.getParameterType() instanceof EnumeratedParameterType) { EnumeratedParameterType ept=(EnumeratedParameterType)para.getParameterType(); if("state".equals(trigger)) { ValueEnumeration enumValue=ept.enumValue(triggerValue); if(enumValue==null) { throw new SpreadsheetLoadException(ctx, "Unknown enumeration value '"+triggerValue+"' for alarm of enumerated parameter "+para.getName()); } else { ept.addAlarm(context, triggerValue, level); } } else { throw new SpreadsheetLoadException(ctx, "Unexpected trigger type '"+trigger+"' for alarm of enumerated parameter "+para.getName()); } } } /** * FIXME: replace the following toMatchCriteria by parseBooleanExpression when the latter is stable * * @param criteriaString * @return */ private MatchCriteria toMatchCriteria(SpaceSystem spaceSystem, String criteriaString) { criteriaString = criteriaString.trim(); if ((criteriaString.startsWith("&(") || criteriaString.startsWith("|(")) && (criteriaString.endsWith(")"))) { return parseBooleanExpression(spaceSystem, criteriaString); } else if(criteriaString.contains(";")) { ComparisonList cl = new ComparisonList(); String splitted[] = criteriaString.split(";"); for (String part: splitted) { cl.comparisons.add(toComparison(spaceSystem, part)); } return cl; } else { return toComparison(spaceSystem, criteriaString); } } /** * Boolean expression has the following pattern: op(epx1;exp2;...;expn) * * op is & (AND) or | (OR) * expi are boolean expression or condition * * A condition is defined as: parametername op value * * value can be * - plain value * - quoted with " or ”. The two quote characters can be used interchangeably . Backslash can be use to escape those double quote. * - $other_parametername * * parametername can be suffixed with .raw * * Top level expression can be in the form epx1;exp2;...;expn which will be transformed into &(epx1;exp2;...;expn) for * compatibility with the previously implemented Comparison * * @param rawExpression * @return */ private BooleanExpression parseBooleanExpression(SpaceSystem spaceSystem, String rawExpression) { String regex = "([\"”])([^\"”\\\\]*(?:\\\\.[^\"”\\\\]*)*)([\"”])"; rawExpression = rawExpression.trim(); // Correct top-level expression if (!rawExpression.startsWith("&") && !rawExpression.startsWith("|")) { rawExpression = "&(" + rawExpression + ")"; } Pattern p = Pattern.compile(regex); Matcher m = p.matcher(rawExpression); ArrayList<String> quotes = new ArrayList<>(); while (m.find()) { quotes.add(rawExpression.substring(m.start(2), m.end(2))); } String spec = p.matcher(rawExpression).replaceAll("\\$\\$"); return toBooleanExpression(spaceSystem, spec, quotes); } private void parseConditionList(SpaceSystem spaceSystem, ExpressionList conditions, String spec, ArrayList<String> quotes) { // Split top-level expressions ArrayList<String> expressions = new ArrayList<>(); int balance = 0; String exp = ""; for (int i = 0; i < spec.length(); i++) { if (spec.charAt(i) == '(') { balance++; } else if (spec.charAt(i) == ')') { balance--; } else if ((spec.charAt(i) == ';') && (balance == 0)) { if (!exp.isEmpty()) { expressions.add(exp); } exp = ""; continue; } exp += spec.charAt(i); } if (!exp.isEmpty()) { expressions.add(exp); } // Parse each expression for (String expression: expressions) { conditions.addConditionExpression(toBooleanExpression(spaceSystem, expression, quotes)); } } private BooleanExpression toBooleanExpression(SpaceSystem spaceSystem, String spec, ArrayList<String> quotes) { spec = spec.trim(); BooleanExpression condition = null; if (spec.startsWith("&(") && (spec.endsWith(")"))) { condition = new ANDedConditions(); parseConditionList(spaceSystem, (ExpressionList)condition, spec.substring(2, spec.length() -1), quotes); } else if (spec.startsWith("|(") && (spec.endsWith(")"))) { condition = new ORedConditions(); parseConditionList(spaceSystem, (ExpressionList)condition, spec.substring(2, spec.length() -1), quotes); } else { condition = toCondition(spaceSystem, spec, quotes); } return condition; } private Condition toCondition(SpaceSystem spaceSystem, String comparisonString, ArrayList<String> quotes) { Matcher m = Pattern.compile("(.*?)(=|!=|<=|>=|<|>)(.*)").matcher(comparisonString); if (!m.matches()) { throw new SpreadsheetLoadException(ctx, "Cannot parse condition '"+comparisonString+"'"); } String lParamName = m.group(1).trim(); boolean lParamCalibrated = true; if (lParamName.endsWith(".raw")) { lParamName = lParamName.substring(0, lParamName.length() - 4); lParamCalibrated = false; } Parameter lParam = spaceSystem.getParameter(lParamName); final ParameterInstanceRef lParamRef = new ParameterInstanceRef(lParam, lParamCalibrated); String op = m.group(2); if ("=".equals(op)) { op = "=="; } String rValue = m.group(3).trim(); String rParamName = null; Parameter rParam = null; final ParameterInstanceRef rParamRef; final Condition cond; if (rValue.startsWith("$$")) { // Quoted values rValue = quotes.remove(0); } if (rValue.startsWith("$")) { boolean rParamCalibrated = true; rParamName = rValue.substring(1); if (rParamName.endsWith(".raw")) { rParamName = rParamName.substring(0, rParamName.length() - 4); rParamCalibrated = false; } rParam = spaceSystem.getParameter(rParamName); rParamRef = new ParameterInstanceRef(rParam, rParamCalibrated); cond = new Condition(OperatorType.stringToOperator(op), lParamRef, rParamRef); } else { rParamRef = null; if((rValue.startsWith("\"")||rValue.startsWith("”")) && (rValue.endsWith("\"")||rValue.endsWith("”"))) { rValue = rValue.substring(1, rValue.length()-1); } cond = new Condition(OperatorType.stringToOperator(op), lParamRef, rValue); } if ((rParamRef != null) && (rParam == null)) { spaceSystem.addUnresolvedReference(new NameReference(rParamName, Type.PARAMETER, new ResolvedAction() { @Override public boolean resolved(NameDescription nd) { rParamRef.setParameter((Parameter) nd); return true; } })); } if (lParam == null) { spaceSystem.addUnresolvedReference(new NameReference(lParamName, Type.PARAMETER, new ResolvedAction() { @Override public boolean resolved(NameDescription nd) { lParamRef.setParameter((Parameter) nd); cond.resolveValueType(); return true; } })); } else { cond.resolveValueType(); } return cond; } private Comparison toComparison(SpaceSystem spaceSystem, String comparisonString) { Matcher m = Pattern.compile("(.*?)(=|!=|<=|>=|<|>)(.*)").matcher(comparisonString); if(!m.matches()) throw new SpreadsheetLoadException(ctx, "Cannot parse condition '"+comparisonString+"'"); String pname=m.group(1).trim(); boolean useCalibrated=true; int idx = pname.indexOf('.'); if(idx!=-1) { String t = pname.substring(idx+1); if("raw".equals(t)) { pname = pname.substring(0, idx); useCalibrated = false; } else { throw new SpreadsheetLoadException(ctx, "Cannot parse parameter for comparison '"+pname+"'. Use parameterName or parameterName.raw"); } } String op=m.group(2); String value=m.group(3).trim(); if((value.startsWith("\"")||value.startsWith("”")) && (value.endsWith("\"")||value.endsWith("”"))) { value = value.substring(1, value.length()-1); } if ("=".equals(op)) { op="=="; } OperatorType opType=Comparison.stringToOperator(op); if(opType==null) { throw new SpreadsheetLoadException(ctx, "Unknown operator '"+op+"'"); } Parameter compareParam = spaceSystem.getParameter(pname); if (compareParam != null) { ParameterInstanceRef pref=new ParameterInstanceRef(compareParam, useCalibrated); Comparison comp = new Comparison(pref, value, opType); comp.resolveValueType(); return comp; } else { final ParameterInstanceRef pref=new ParameterInstanceRef(false); final Comparison ucomp = new Comparison(pref, value, opType); spaceSystem.addUnresolvedReference(new NameReference(pname, Type.PARAMETER, new ResolvedAction() { @Override public boolean resolved(NameDescription nd) { pref.setParameter((Parameter) nd); ucomp.resolveValueType(); return true; } })); return ucomp; } } protected boolean hasColumn(Cell[] cells, int idx) { return (cells!=null) && (cells.length>idx) && (cells[idx].getContents()!=null) && (!cells[idx].getContents().equals("")); } private int getSize(Parameter param, SequenceContainer sc) { // either we have a Parameter or we have a SequenceContainer, we cannot have both or neither if (param != null) { DataEncoding de = ((BaseDataType)param.getParameterType()).getEncoding(); if(de==null) { throw new SpreadsheetLoadException(ctx, "Cannot determine the data encoding for "+param.getName()); } if ((de instanceof FloatDataEncoding) || (de instanceof IntegerDataEncoding) || (de instanceof BinaryDataEncoding) ||(de instanceof BooleanDataEncoding)){ return de.sizeInBits; } else if (de instanceof StringDataEncoding) { return -1; } else { throw new SpreadsheetLoadException(ctx, "No known size for data encoding : " + de); } } else { return sc.sizeInBits; } } /** If repeat != "", decodes it to either an integer or a parameter and adds it to the SequenceEntry * If repeat is an integer, this integer is returned */ private int addRepeat(SequenceEntry se, String repeat) { if (!repeat.equals("")) { se.repeatEntry = new Repeat(); try { int rep = Integer.decode(repeat); se.repeatEntry.count = new FixedIntegerValue(rep); return rep; } catch (NumberFormatException e) { se.repeatEntry.count = new DynamicIntegerValue(); Parameter repeatparam = parameters.get(repeat); if(repeatparam==null) { throw new SpreadsheetLoadException(ctx, "Cannot find the parameter for repeat "+repeat); } ((DynamicIntegerValue)se.repeatEntry.count).setParameterInstanceRef(new ParameterInstanceRef(repeatparam, true)); return -1; } } else { return 1; } } protected Sheet switchToSheet(String sheetName, boolean required) { Sheet sheet = workbook.getSheet(sheetName); ctx.sheet=sheetName; ctx.row=0; if (required && sheet==null) { throw new SpreadsheetLoadException(ctx, "Required sheet '"+sheetName+"' was found missing"); } return sheet; } protected Cell[] jumpToRow(Sheet sheet, int row) { ctx.row=row+1; return sheet.getRow(row); } protected String requireString(Cell[] cells, int column) { String contents = cells[column].getContents(); if("".equals(contents)) { char col=(char) ('A'+(char)column); throw new SpreadsheetLoadException(ctx, "Cell at "+col+ctx.row+" is required"); } return contents; } /** * Temporary value holder for the enumeration definition (because * enumerations are read before parameters, and reading sharing the same EPT * among all parameters is not a good approach (think different alarm * definitions) */ protected static class EnumerationDefinition { public LinkedHashMap<Long,String> valueMap=new LinkedHashMap<Long,String>(); } /** * Anomaly that maybe turns out to be fine, when more sheets of the spreadsheet have been read. */ private abstract class PotentialExtractionError { private SpreadsheetLoadException exc; PotentialExtractionError(SpreadsheetLoadContext ctx, String error) { exc=new SpreadsheetLoadException(ctx, error); } abstract boolean errorPersists(); public void recheck() { if(errorPersists()) { throw exc; } } } static Pattern allowedInNameType = Pattern.compile("[\\d\\w_-]+"); boolean draconicXtceNameRestrictions = true; //TODO - should be configurable private void validateNameType(String name) { if(!draconicXtceNameRestrictions) { return; } if(!allowedInNameType.matcher(name).matches()) { throw new SpreadsheetLoadException(ctx, "Invalid name '"+name+"'; should only contain letters, digits, _, and -"); } } }