content: package com.tsnsched.core.sched2netconf;
  //TSNsched uses the Z3 theorem solver to generate traffic schedules for Time Sensitive Networking (TSN)
  //
  //    TSNsched is licensed under the GNU GPL version 2 or later.
  //    
  //    You should have received a copy of the GNU General Public License
  //    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  
  
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.List;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import javax.xml.XMLConstants;
  import javax.xml.parsers.DocumentBuilder;
  import javax.xml.parsers.DocumentBuilderFactory;
  import javax.xml.parsers.ParserConfigurationException;
  import javax.xml.transform.OutputKeys;
  import javax.xml.transform.Transformer;
  import javax.xml.transform.TransformerException;
  import javax.xml.transform.TransformerFactory;
  import javax.xml.transform.dom.DOMSource;
  import javax.xml.transform.stream.StreamResult;
  import org.w3c.dom.Comment;
  import org.w3c.dom.Document;
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  
  import com.tsnsched.core.components.Cycle;
  import com.tsnsched.core.components.Port;
  import com.tsnsched.core.network.*;
  import com.tsnsched.core.nodes.*;
  import com.tsnsched.core.nodes.Switch;
  
  
  /** Transforms the schedule generated by TSNsched to XML. */
  public class XMLExporter {
  
    private static final Logger logger = Logger.getLogger(XMLExporter.class.getName());
    private Document doc;
    private Element portsTag;
    private Network net;
    private int gclIndex = 0;
    private static final int OPEN_GATES_IN_GAP = 1; // 1 for queue 0, 255 to open all queues
  
    private static final long MAX_UINT32 = Long.parseUnsignedLong("4294967295");
    private static final String CURRENT_DIR = System.getProperty("user.dir");
    private static final String CURRENT_TIME =
        new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSS").format(new Date());
    private static final String XML = ".xml";
    private static final String SLASH = "/";
    private static final String GUARDBAND = "Guard Band";
  
    private static final String ROOT1 = "urn:ietf:params:xml:ns:netconf:base:1.0";
    private static final String ROOT2 = "nc:config";
    private static final String TNTAS1 = "urn:sysrepo:TrustNode:TNsysrepo";
    private static final String TNTAS2 = "TNtas";
    private static final String PORTS = "ports";
    private static final String ID = "id";
  
    /**
     * Constructor of the XMLExporter class.
     *
     * @param net input network topology
     */
    public XMLExporter(Network net) {
      this.net = net;
      writeDataToXML();
    }
  
    private void setupXMLNodes(String switchName) {
      // Get Document Builder
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  
      try {
        // prevent XXE attack
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
  
        DocumentBuilder builder;
        builder = factory.newDocumentBuilder();
  
        // Build document
        this.doc = builder.newDocument();
  
        // setup default nodes
        Element root = doc.createElementNS(ROOT1, ROOT2);
        Element tnTAS = doc.createElementNS(TNTAS1, TNTAS2);
        portsTag = doc.createElement(PORTS);
  
        // append nodes
        doc.appendChild(root);
        root.appendChild(tnTAS);
        tnTAS.appendChild(portsTag);
  
        Comment comment = doc.createComment("Created with TSNsched. Schedule for " + switchName);
        root.insertBefore(comment, root.getFirstChild());
  
      } catch (ParserConfigurationException e) {
        logger.log(Level.SEVERE, "Something went wrong: {0} ", e);
      }
    }
  
    /**
     * Utility method to create GCL entries.
     *
     * @param id of the GCl entry
     * @param timePeriod how long the gateState is open.
     * @param gateState which gates to open (binary encoded)
     * @return Node Object
     */
    private Node createGCL(int id, long timePeriod, int gateState, boolean isGuardBand) {
      Element gateControlList = doc.createElement("GCL");
      if (isGuardBand) {
        Comment guardBand = doc.createComment(GUARDBAND);
        gateControlList.appendChild(guardBand);
      }
      gateControlList.appendChild(createTagWithValue("id", id));
      gateControlList.appendChild(createTagWithValue("timeperiod", timePeriod));
      gateControlList.appendChild(createTagWithValue("gatestates", gateState));
      return gateControlList;
    }
  
    /**
     * Utility method to create XML text node.
     *
     * @param name tag name
     * @param value value of the tag
     * @return Node Object
     */
    private Node createTagWithValue(String name, Object value) {
      Element node = doc.createElement(name);
      node.appendChild(doc.createTextNode(String.valueOf(value))); // convert Object to String
      return node;
    }
  
    /** Utility method to write options to the XML. */
    private void writOptions() {
      portsTag.appendChild(createTagWithValue("admin_base_time", 0));
      portsTag.appendChild(createTagWithValue("admin_cycle_time_ext", 0));
      portsTag.appendChild(createTagWithValue("gate_enable", true));
    }
  
    /** Iterate over the network and write values to the Document Object Model. */
    private void writeDataToXML() {
      // Iterate over network switches
      for (Switch currentSwitch : net.getSwitches()) {
        if (currentSwitch instanceof TSNSwitch) {
          setupXMLNodes(currentSwitch.getName());
          // iterate over ports of each switch
          for (Port currentPort : ((TSNSwitch) currentSwitch).getPorts()) {
            // check if given port has flowfragments
            if (!currentPort.getFlowFragments().isEmpty()) {
              List<Triple> allSlots = new ArrayList<>();
              // egress port, so get port ID
              portsTag.appendChild(createTagWithValue(ID, currentPort.getPortNum()));
  
              Cycle currentCycle = currentPort.getCycle();
              Comment comment =
                  doc.createComment("Cycle duration: " + currentCycle.getCycleDuration());
              portsTag.appendChild(comment);
  
              for (int i = 0; i < currentCycle.getSlotsUsed().size(); i++) {
                int currentQ = currentCycle.getSlotsUsed().get(i);
  
                //convert queue ID to binary encoded value
                int binCurrentQ = (int) Math.pow(2,currentQ);
  
                // find all slot durations for Q i
                // second loop, because there could be more slots than priorities
                for (int j = 0; j < currentCycle.getSlotDuration().get(i).size(); j++) {
                  long slotDuration = (long) currentCycle.getSlotDuration(currentQ, j);
                  // slot with length 0 are not used
                  if (slotDuration == 0L) {
                    continue;
                  }
                  // check if slot duration is smaller than an unsigned 32 bit integer
                  // (type provided by TrustNode yang model) roughly 4,29 seconds
                  if (slotDuration <= MAX_UINT32) {
                    long slotStart = (long) currentCycle.getSlotStart(currentQ, j);
  
                    // sort all values into a list of triples
                    allSlots.add(new Triple(binCurrentQ, slotStart, slotDuration));
  
                  } else {
                    throw new NumberFormatException("Cycle duration too large.");
                  }
                }
              }
              // done with all slots, now sort list by slotStart
              allSlots.sort(Triple::compareTo);
  
              // get queue / priorities
              long prevSlotStart = 0;
              long prevSlotDur = 0;
              long slotSum = 0;
              long supposedSlotStart = 0;
  
              int i = 0;
              // iterate of slotList and write the entries
              for (Triple triple : allSlots) {
                // is there a gap before the current Slot?
                supposedSlotStart = prevSlotStart + prevSlotDur;
                if (supposedSlotStart != triple.getSlotStart()) {
                  // create GCl for best effort traffic (open just Q0)
                  portsTag.appendChild(
                      createGCL(
                          gclIndex++,
                          triple.getSlotStart() - slotSum - (long) currentPort.getGbSize(),
                          OPEN_GATES_IN_GAP,
                          false));
  
                  // close all queues inside guard bands
                  portsTag.appendChild(
                      createGCL(gclIndex++, (long) currentPort.getGbSize(), 0, true));
                }
                // no gap before current slot
                portsTag.appendChild(
                    createGCL(gclIndex++, triple.getSlotDuration(), triple.getQueueID(), false));
                slotSum = triple.getSlotStart() + triple.getSlotDuration();
                prevSlotDur = triple.getSlotDuration();
                prevSlotStart = triple.getSlotStart();
  
                // are we at the last slot of the last queue? Is there a gap after it?
                if (++i == allSlots.size() && currentCycle.getCycleDuration() - slotSum != 0) {
                  portsTag.appendChild(
                      createGCL(
                          gclIndex++,
                          (long) (currentCycle.getCycleDuration() - slotSum),
                          OPEN_GATES_IN_GAP,
                          false));
                }
              }
            }
          }
          writOptions();
          prettyPrint(false, true, currentSwitch.getName());
        }
      }
    }
  
    /**
     * Outputs the Document Object Model to console and/or file.
     *
     * @param printToConsole if true, XML gets printed to the console
     * @param writeToFile if true, XML gets written to file
     * @param switchName name of the current switch
     */
    private void prettyPrint(boolean printToConsole, boolean writeToFile, String switchName) {
      try {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        Transformer transformer = transformerFactory.newTransformer();
  
        // output settings
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
  
        // System.out.println("BAGUI: " + CURRENT_DIR + SLASH + "XMLExporterFiles" + SLASH + CURRENT_TIME + "-" + switchName + XML);
        
        DOMSource source = new DOMSource(this.doc);
  
        if (printToConsole) {
          // write to console
          StreamResult console = new StreamResult(System.out);
          transformer.transform(source, console);
        }
        if (writeToFile) {
          // write to file
          StreamResult file =
              new StreamResult(
                  new FileOutputStream(CURRENT_DIR + SLASH + "XMLExporterFiles" + SLASH + CURRENT_TIME + "-" + switchName + XML));
          transformer.transform(source, file);
        }
      } catch (TransformerException | FileNotFoundException e) {
        logger.log(Level.SEVERE, "Something went wrong: {0} ", e);
      }
    }
  }
  