/* * RocksimHandler.java * */ package net.sf.openrocket.file.rocksim.importt; import java.util.HashMap; import net.sf.openrocket.aerodynamics.Warning; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.file.DocumentLoadingContext; import net.sf.openrocket.file.rocksim.RocksimCommonConstants; import net.sf.openrocket.file.simplesax.AbstractElementHandler; import net.sf.openrocket.file.simplesax.ElementHandler; import net.sf.openrocket.file.simplesax.PlainTextHandler; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.Stage; import org.xml.sax.SAXException; /** * This class is a Sax element handler for Rocksim version 9 design files. It parses the Rocksim file (typically * a .rkt extension) and creates corresponding OpenRocket components. This is a best effort approach and may not * be an exact replica. * <p/> * Limitations: Rocksim flight simulations are not imported; tube fins are not supported; Rocksim 'pods' are not supported. */ public class RocksimHandler extends AbstractElementHandler { /** * The main content handler. */ private RocksimContentHandler handler = null; private final DocumentLoadingContext context; public RocksimHandler(DocumentLoadingContext context) { super(); this.context = context; } /** * Return the OpenRocketDocument read from the file, or <code>null</code> if a document * has not been read yet. * * @return the document read, or null. */ public OpenRocketDocument getDocument() { return context.getOpenRocketDocument(); } @Override public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) { // Check for unknown elements if (!element.equals("RockSimDocument")) { warnings.add(Warning.fromString("Unknown element " + element + ", ignoring.")); return null; } // Check for first call if (handler != null) { warnings.add(Warning.fromString("Multiple document elements found, ignoring later " + "ones.")); return null; } handler = new RocksimContentHandler(context); return handler; } } /** * Handles the content of the <DesignInformation> tag. */ class RocksimContentHandler extends AbstractElementHandler { /** * The DocumentLoadingContext */ private final DocumentLoadingContext context; /** * The top-level component, from which all child components are added. */ private final Rocket rocket; /** * The rocksim file version. */ private String version; public RocksimContentHandler(DocumentLoadingContext context) { super(); this.context = context; this.rocket = context.getOpenRocketDocument().getRocket(); } /** * Get the OpenRocket document that has been created from parsing the Rocksim design file. * * @return the instantiated OpenRocketDocument */ public OpenRocketDocument getDocument() { return context.getOpenRocketDocument(); } @Override public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) { if (RocksimCommonConstants.DESIGN_INFORMATION.equals(element)) { //The next sub-element is "RocketDesign", which is really the only thing that matters. Rather than //create another handler just for that element, handle it here. return this; } if (RocksimCommonConstants.FILE_VERSION.equals(element)) { return PlainTextHandler.INSTANCE; } if (RocksimCommonConstants.ROCKET_DESIGN.equals(element)) { return new RocketDesignHandler(context, rocket); } return null; } @Override public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException { /** * SAX handler for Rocksim file version number. The value is not used currently, but could be used in the future * for backward/forward compatibility reasons (different lower level handlers could be called via a strategy pattern). */ if (RocksimCommonConstants.FILE_VERSION.equals(element)) { version = content; } } /** * Answer the file version. * * @return the version of the Rocksim design file */ public String getVersion() { return version; } } /** * A SAX handler for the high level Rocksim design. This structure includes sub-structures for each of the stages. * Correct functioning of this handler is predicated on the stage count element appearing before the actual stage parts * structures. If that invariant is not true, then behavior will be unpredictable. */ class RocketDesignHandler extends AbstractElementHandler { private final DocumentLoadingContext context; /** * The parent component. */ private final RocketComponent component; /** * The parsed stage count. Defaults to 1. */ private int stageCount = 1; /** * The overridden stage 1 mass. */ private double stage1Mass = 0d; /** * The overridden stage 2 mass. */ private double stage2Mass = 0d; /** * The overridden stage 3 mass. */ private double stage3Mass = 0d; /** * The overridden stage 1 Cg. */ private double stage1CG = 0d; /** * The overridden stage 2 Cg. */ private double stage2CG = 0d; /** * The overridden stage 3 Cg. */ private double stage3CG = 0d; /** * Constructor. * * @param c the parent component */ public RocketDesignHandler(DocumentLoadingContext context, RocketComponent c) { this.context = context; component = c; } @Override public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) { /** * In Rocksim stages are from the top down, so a single stage rocket is actually stage '3'. A 2-stage * rocket defines stage '2' as the initial booster with stage '3' sitting atop it. And so on. */ if ("Stage3Parts".equals(element)) { final Stage stage = new Stage(); if (stage3Mass > 0.0d) { stage.setMassOverridden(true); stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override stage.setOverrideMass(stage3Mass); } if (stage3CG > 0.0d) { stage.setCGOverridden(true); stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override stage.setOverrideCGX(stage3CG); } component.addChild(stage); return new StageHandler(context, stage); } if ("Stage2Parts".equals(element)) { if (stageCount >= 2) { final Stage stage = new Stage(); if (stage2Mass > 0.0d) { stage.setMassOverridden(true); stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override stage.setOverrideMass(stage2Mass); } if (stage2CG > 0.0d) { stage.setCGOverridden(true); stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override stage.setOverrideCGX(stage2CG); } component.addChild(stage); return new StageHandler(context, stage); } } if ("Stage1Parts".equals(element)) { if (stageCount == 3) { final Stage stage = new Stage(); if (stage1Mass > 0.0d) { stage.setMassOverridden(true); stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override stage.setOverrideMass(stage1Mass); } if (stage1CG > 0.0d) { stage.setCGOverridden(true); stage.setOverrideSubcomponents(true); //Rocksim does not support this type of override stage.setOverrideCGX(stage1CG); } component.addChild(stage); return new StageHandler(context, stage); } } if (RocksimCommonConstants.NAME.equals(element)) { return PlainTextHandler.INSTANCE; } if ("StageCount".equals(element)) { return PlainTextHandler.INSTANCE; } if ("Stage3Mass".equals(element)) { return PlainTextHandler.INSTANCE; } if ("Stage2Mass".equals(element)) { return PlainTextHandler.INSTANCE; } if ("Stage1Mass".equals(element)) { return PlainTextHandler.INSTANCE; } if ("Stage3CG".equals(element)) { return PlainTextHandler.INSTANCE; } if ("Stage2CGAlone".equals(element)) { return PlainTextHandler.INSTANCE; } if ("Stage1CGAlone".equals(element)) { return PlainTextHandler.INSTANCE; } return null; } @Override public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException { try { if (RocksimCommonConstants.NAME.equals(element)) { component.setName(content); } if ("StageCount".equals(element)) { stageCount = Integer.parseInt(content); } if ("Stage3Mass".equals(element)) { stage3Mass = Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_MASS; } if ("Stage2Mass".equals(element)) { stage2Mass = Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_MASS; } if ("Stage1Mass".equals(element)) { stage1Mass = Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_MASS; } if ("Stage3CG".equals(element)) { stage3CG = Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH; } if ("Stage2CGAlone".equals(element)) { stage2CG = Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH; } if ("Stage1CGAlone".equals(element)) { stage1CG = Double.parseDouble(content) / RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH; } } catch (NumberFormatException nfe) { warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number."); } } } /** * A SAX handler for a Rocksim stage. */ class StageHandler extends AbstractElementHandler { private final DocumentLoadingContext context; /** * The parent OpenRocket component. */ private final RocketComponent component; /** * Constructor. * * @param c the parent component * @throws IllegalArgumentException thrown if <code>c</code> is null */ public StageHandler(DocumentLoadingContext context, RocketComponent c) throws IllegalArgumentException { if (c == null) { throw new IllegalArgumentException("The stage component may not be null."); } this.context = context; component = c; } @Override public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) { if (RocksimCommonConstants.NOSE_CONE.equals(element)) { return new NoseConeHandler(context, component, warnings); } if (RocksimCommonConstants.BODY_TUBE.equals(element)) { return new BodyTubeHandler(context, component, warnings); } if (RocksimCommonConstants.TRANSITION.equals(element)) { return new TransitionHandler(context, component, warnings); } return null; } }