// Connect all input ports to their sources and return all connections formed.
 public List<ConnectionValue> connect()
     throws UndeclaredIdentifierException, UndeclaredAttributeException, InvalidAttributeException,
         TypeMismatchException {
   List<ConnectionValue> connections = new LinkedList<>();
   for (Map.Entry<String, FuturePortValue> e : futureInputPorts.entrySet()) {
     String inputPortName = e.getKey();
     FuturePortValue source = e.getValue();
     PortValue sourcePort = source.getPort();
     PortValue targetPort = node.getPort(inputPortName);
     // TODO connection attributes; for now we assume none
     Map<String, Value> attrs = new HashMap<>();
     ConnectionValue conn = new ConnectionValue(sourcePort, targetPort, attrs);
     connections.add(conn);
   }
   return connections;
 }
  @Override
  public void elaborate() throws Exception {
    // prevent infinite recursive elaboration
    if (futureOutputPorts != null) {
      return;
    }
    log.debug("type signature is " + signature.toString());
    // decompose signature into (input) -> (output)
    TupleTypeValue inputType = (TupleTypeValue) signature.getInputType();
    TupleTypeValue outputType = (TupleTypeValue) signature.getOutputType();
    // construct values for all future output ports
    // simultaneously build a collection of all input port names
    Set<String> inputPortNames = new HashSet<>();
    Set<String> outputPortNames = new HashSet<>();
    inputPortNames.addAll(nodeType.getPorts().keySet());
    MappedArray<String, Value> futurePortMap = new MappedArray<>();

    for (MappedArray<String, TypeValue>.Entry typeEntry : outputType.getSubtypes()) {
      String outputPortName = typeEntry.getKey();
      PortTypeValue outputPortType = nodeType.getPorts().get(outputPortName);
      FuturePortValue futurePort = new FuturePortValue(this, outputPortName, outputPortType);
      futurePortMap.put(outputPortName, futurePort);
      inputPortNames.remove(outputPortName);
      outputPortNames.add(outputPortName);
    }
    futureOutputPorts = new TupleValue(outputType, futurePortMap);

    inputEdge.getSource().elaborate();
    Value inputValue = inputEdge.getSource().getValue();
    TupleValue input = (TupleValue) inputValue;

    // now we have enough information to disassemble `input`
    // into attributes and (future) input ports

    Map<String, Value> nodeAttrs = new HashMap<>();
    Map<String, Map<String, Value>> portAttrs = new HashMap<>();

    for (String inputPortName : inputPortNames) {
      PortTypeValue inputPortType = nodeType.getPorts().get(inputPortName);
      Value inputPortValue = input.getEntry(inputPortName);
      // if the port has attributes, this is a tuple
      // (0: FuturePortValue, 1: (attributes))
      // otherwise, this is just a FuturePortValue
      FuturePortValue futurePort;
      Map<String, Value> inputPortAttrs;
      if (inputPortType.getAttributes().isEmpty()) {
        // in the case where a single output port is directly used as input,
        // this is a tuple of size 1 that needs to be "unwrapped" first
        futurePort = unwrapPort(inputPortValue);
        futureInputPorts.put(inputPortName, futurePort);
        inputPortAttrs = new HashMap<>(); // no attributes
      } else {
        TupleValue inputPortTuple = (TupleValue) inputPortValue;
        // same story here about unwrapping the port
        futurePort = unwrapPort(inputPortTuple.getEntry(0));
        TupleValue attributesValue = (TupleValue) inputPortTuple.getEntry(1);
        // TODO this can probably be done better, it assumes that all tuple values are named
        inputPortAttrs = MappedArray.toMap(attributesValue.getEntries());
      }
      futureInputPorts.put(inputPortName, futurePort);
      portAttrs.put(inputPortName, inputPortAttrs);
    }

    // we must also get output port attributes, which appear as function inputs
    for (String outputPortName : outputPortNames) {
      PortTypeValue outputPortType = nodeType.getPorts().get(outputPortName);
      Map<String, Value> outputPortAttrs;
      if (outputPortType.getAttributes().isEmpty()) {
        outputPortAttrs = new HashMap<>(); // no attributes
      } else {
        TupleValue attributesValue = (TupleValue) input.getEntry(outputPortName);
        outputPortAttrs = MappedArray.toMap(attributesValue.getEntries());
      }
      portAttrs.put(outputPortName, outputPortAttrs);
    }

    for (String attrName : nodeType.getAttributes().keySet()) {
      Value attrValue = input.getEntry(attrName);
      nodeAttrs.put(attrName, attrValue);
    }

    node = new NodeValue(nodeType, nodeAttrs, portAttrs);
  }
 @Override
 public ExpressionVertex copy(ExpressionGraph g, Map<ExpressionEdge, ExpressionEdge> edgeMap) {
   Preconditions.checkArgument(edgeMap.containsKey(inputEdge));
   return new NodeValueVertex(g, nodeType, signature, edgeMap.get(inputEdge));
 }