private void recordTripUpdate(TripUpdate tripUpdate, DataCopier tuCopier, DataCopier stCopier)
      throws SQLException {
    PreparedStatement stmt = null;

    int updateId = getUpdateId();

    DataCopierRow tuRow = null;

    if (tuCopier != null) {
      tuRow = new DataCopierRow();
    } else {
      stmt = mStatements.get(STTRIPUPDATE);
    }

    if (tuRow == null) {
      stmt.setInt(1, updateId);
    } else {
      tuRow.add(updateId);
    }

    if (tripUpdate.hasTimestamp()) {
      if (tuRow == null) {
        stmt.setLong(2, tripUpdate.getTimestamp());
      } else {
        tuRow.add(tripUpdate.getTimestamp());
      }
    } else {
      if (tuRow == null) {
        stmt.setNull(2, Types.INTEGER);
      } else {
        tuRow.addNull();
      }
    }

    if (tripUpdate.hasTrip()) {
      TripDescriptor trip = tripUpdate.getTrip();

      if (trip.hasScheduleRelationship()) {
        if (tuRow == null) {
          stmt.setInt(3, trip.getScheduleRelationship().getNumber());
        } else {
          tuRow.add(trip.getScheduleRelationship().getNumber());
        }
      } else {
        if (tuRow == null) {
          stmt.setNull(3, Types.INTEGER);
        } else {
          tuRow.addNull();
        }
      }

      if (trip.hasStartDate()) {
        if (tuRow == null) {
          stmt.setString(4, trip.getStartDate());
        } else {
          tuRow.add(trip.getStartDate());
        }
      } else {
        if (tuRow == null) {
          stmt.setNull(4, Types.VARCHAR);
        } else {
          tuRow.addNull();
        }
      }

      if (trip.hasStartTime()) {
        if (tuRow == null) {
          stmt.setString(5, trip.getStartTime());
        } else {
          tuRow.add(trip.getStartTime());
        }
      } else {
        if (tuRow == null) {
          stmt.setNull(5, Types.VARCHAR);
        } else {
          tuRow.addNull();
        }
      }

      if (trip.hasTripId()) {
        if (tuRow == null) {
          stmt.setString(6, trip.getTripId());
        } else {
          tuRow.add(trip.getTripId());
        }
      } else {
        if (tuRow == null) {
          stmt.setNull(6, Types.VARCHAR);
        } else {
          tuRow.addNull();
        }
      }

      if (trip.hasRouteId()) {
        if (tuRow == null) {
          stmt.setString(7, trip.getRouteId());
        } else {
          tuRow.add(trip.getRouteId());
        }
      } else {
        if (tuRow == null) {
          stmt.setNull(7, Types.VARCHAR);
        } else {
          tuRow.addNull();
        }
      }
    } else {
      if (tuRow == null) {
        stmt.setNull(3, Types.INTEGER);
        stmt.setNull(4, Types.VARCHAR);
        stmt.setNull(5, Types.VARCHAR);
        stmt.setNull(6, Types.VARCHAR);
        stmt.setNull(7, Types.VARCHAR);
      } else {
        tuRow.addNull(5);
      }
    }

    if (tripUpdate.hasVehicle()) {
      VehicleDescriptor vd = tripUpdate.getVehicle();

      if (vd.hasId()) {
        if (tuRow == null) {
          stmt.setString(8, vd.getId());
        } else {
          tuRow.add(vd.getId());
        }
      } else {
        if (tuRow == null) {
          stmt.setNull(8, Types.INTEGER);
        } else {
          tuRow.addNull();
        }
      }

      if (vd.hasLabel()) {
        if (tuRow == null) {
          stmt.setString(9, vd.getLabel());
        } else {
          tuRow.add(vd.getLabel());
        }
      } else {
        if (tuRow == null) {
          stmt.setNull(9, Types.VARCHAR);
        } else {
          tuRow.addNull();
        }
      }

      if (vd.hasLicensePlate()) {
        if (tuRow == null) {
          stmt.setString(10, vd.getLicensePlate());
        } else {
          tuRow.add(vd.getLicensePlate());
        }
      } else {
        if (tuRow == null) {
          stmt.setNull(10, Types.VARCHAR);
        } else {
          tuRow.addNull();
        }
      }
    } else {
      if (tuRow == null) {
        stmt.setNull(8, Types.INTEGER);
        stmt.setNull(9, Types.VARCHAR);
        stmt.setNull(10, Types.VARCHAR);
      } else {
        tuRow.addNull(3);
      }
    }

    Date recorded = new Date();

    if (tuRow == null) {
      stmt.setInt(11, (int) (recorded.getTime() / 1000));
    } else {
      tuRow.add((int) (recorded.getTime() / 1000));
    }

    if (tuCopier == null) {
      stmt.addBatch();
    } else {
      tuCopier.add(tuRow);
    }

    if (stCopier == null) {
      stmt = mStatements.get(STTRIPUPDATE_STOPTIMEUPDATES);
    } else {
      stmt = null;
    }

    for (StopTimeUpdate stu : tripUpdate.getStopTimeUpdateList()) {
      DataCopierRow stRow = null;

      if (stmt == null) {
        stRow = new DataCopierRow();
      }

      if (stRow == null) {
        stmt.setInt(1, updateId);
      } else {
        stRow.add(updateId);
      }

      if (stu.hasArrival()) {
        StopTimeEvent ste = stu.getArrival();

        if (ste.hasTime()) {
          if (stRow == null) {
            stmt.setLong(2, ste.getTime());
          } else {
            stRow.add(ste.getTime());
          }
        } else {
          if (stRow == null) {
            stmt.setNull(2, Types.INTEGER);
          } else {
            stRow.addNull();
          }
        }

        if (ste.hasUncertainty()) {
          if (stRow == null) {
            stmt.setInt(3, ste.getUncertainty());
          } else {
            stRow.add(ste.getUncertainty());
          }
        } else {
          if (stRow == null) {
            stmt.setNull(3, Types.INTEGER);
          } else {
            stRow.addNull();
          }
        }

        if (ste.hasDelay()) {
          if (stRow == null) {
            stmt.setInt(4, ste.getDelay());
          } else {
            stRow.add(ste.getDelay());
          }
        } else {
          if (stRow == null) {
            stmt.setNull(4, Types.INTEGER);
          } else {
            stRow.addNull();
          }
        }
      } else {
        if (stRow == null) {
          stmt.setNull(2, Types.INTEGER);
          stmt.setNull(3, Types.INTEGER);
          stmt.setNull(4, Types.INTEGER);
        } else {
          stRow.addNull(3);
        }
      }

      if (stu.hasDeparture()) {
        StopTimeEvent ste = stu.getDeparture();

        if (ste.hasTime()) {
          if (stRow == null) {
            stmt.setLong(5, ste.getTime());
          } else {
            stRow.add(ste.getTime());
          }
        } else {
          if (stRow == null) {
            stmt.setNull(5, Types.INTEGER);
          } else {
            stRow.addNull();
          }
        }

        if (ste.hasUncertainty()) {
          if (stRow == null) {
            stmt.setInt(6, ste.getUncertainty());
          } else {
            stRow.add(ste.getUncertainty());
          }
        } else {
          if (stRow == null) {
            stmt.setNull(6, Types.INTEGER);
          } else {
            stRow.addNull();
          }
        }

        if (ste.hasDelay()) {
          if (stRow == null) {
            stmt.setInt(7, ste.getDelay());
          } else {
            stRow.add(ste.getDelay());
          }
        } else {
          if (stRow == null) {
            stmt.setNull(7, Types.INTEGER);
          } else {
            stRow.addNull();
          }
        }
      } else {
        if (stRow == null) {
          stmt.setNull(5, Types.INTEGER);
          stmt.setNull(6, Types.INTEGER);
          stmt.setNull(7, Types.INTEGER);
        } else {
          stRow.addNull(3);
        }
      }

      int srInt =
          stu.hasScheduleRelationship()
              ? stu.getScheduleRelationship().getNumber()
              : com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate
                  .ScheduleRelationship.SCHEDULED_VALUE;
      if (stRow == null) {
        stmt.setInt(8, srInt);
      } else {
        stRow.add(srInt);
      }

      if (stu.hasStopId()) {
        if (stRow == null) {
          stmt.setString(9, stu.getStopId());
        } else {
          stRow.add(stu.getStopId());
        }
      } else {
        if (stRow == null) {
          stmt.setNull(9, Types.VARCHAR);
        } else {
          stRow.addNull();
        }
      }

      int ssInt = stu.hasStopSequence() ? stu.getStopSequence() : -1;

      if (stRow == null) {
        stmt.setInt(10, ssInt);
      } else {
        stRow.add(ssInt);
      }

      if (stmt == null) {
        stCopier.add(stRow);
      } else {
        stmt.addBatch();
      }
    }
  }
  private void recordVehicle(VehiclePosition vehicle, DataCopier copier)
      throws SQLException, Exception {
    if (!vehicle.hasPosition()) {
      throw new Exception("No position found");
    }

    PreparedStatement stmt = null;

    DataCopierRow row = null;

    if (copier == null) {
      stmt = mStatements.get(STVEHICLE);
    } else {
      row = new DataCopierRow();
    }

    int congestionLevel =
        vehicle.hasCongestionLevel()
            ? vehicle.getCongestionLevel().getNumber()
            : CongestionLevel.UNKNOWN_CONGESTION_LEVEL_VALUE;
    int vehicleStatus =
        vehicle.hasCurrentStatus()
            ? vehicle.getCurrentStatus().getNumber()
            : VehicleStopStatus.IN_TRANSIT_TO_VALUE;
    int stopSequence = vehicle.hasCurrentStopSequence() ? vehicle.getCurrentStopSequence() : -1;

    if (row == null) {
      stmt.setInt(1, congestionLevel);
      stmt.setInt(2, vehicleStatus);
      stmt.setInt(3, stopSequence);
    } else {
      row.add(congestionLevel);
      row.add(vehicleStatus);
      row.add(stopSequence);
    }

    Position pos = vehicle.getPosition();

    if (pos.hasBearing()) {
      if (row == null) {
        stmt.setFloat(4, pos.getBearing());
      } else {
        row.add(pos.getBearing());
      }
    } else {
      if (row == null) {
        stmt.setNull(4, Types.FLOAT);
      } else {
        row.addNull();
      }
    }

    if (pos.hasOdometer()) {
      if (row == null) {
        stmt.setDouble(5, pos.getOdometer());
      } else {
        row.add(pos.getOdometer());
      }
    } else {
      if (row == null) {
        stmt.setNull(5, Types.DOUBLE);
      } else {
        row.addNull();
      }
    }

    if (pos.hasSpeed()) {
      if (row == null) {
        stmt.setFloat(6, pos.getSpeed());
      } else {
        row.add(pos.getSpeed());
      }
    } else {
      if (row == null) {
        stmt.setNull(6, Types.FLOAT);
      } else {
        row.addNull();
      }
    }

    if (pos.hasLatitude()) {
      if (row == null) {
        stmt.setFloat(7, pos.getLatitude());
      } else {
        row.add(pos.getLatitude());
      }
    } else {
      if (row == null) {
        stmt.setNull(7, Types.FLOAT);
      } else {
        row.addNull();
      }
    }

    if (pos.hasLongitude()) {
      if (row == null) {
        stmt.setFloat(8, pos.getLongitude());
      } else {
        row.add(pos.getLongitude());
      }
    } else {
      if (row == null) {
        stmt.setNull(8, Types.FLOAT);
      } else {
        row.addNull();
      }
    }

    if (vehicle.hasStopId()) {
      if (row == null) {
        stmt.setString(9, vehicle.getStopId());
      } else {
        row.add(vehicle.getStopId());
      }
    } else {
      if (row == null) {
        stmt.setNull(9, Types.VARCHAR);
      } else {
        row.addNull();
      }
    }

    if (vehicle.hasTimestamp()) {
      if (row == null) {
        stmt.setLong(10, vehicle.getTimestamp());
      } else {
        row.add(vehicle.getTimestamp());
      }
    } else {
      if (row == null) {
        stmt.setNull(10, Types.INTEGER);
      } else {
        row.addNull();
      }
    }

    if (vehicle.hasTrip()) {
      TripDescriptor trip = vehicle.getTrip();

      if (trip.hasScheduleRelationship()) {
        if (row == null) {
          stmt.setInt(11, trip.getScheduleRelationship().getNumber());
        } else {
          row.add(trip.getScheduleRelationship().getNumber());
        }
      } else {
        if (row == null) {
          stmt.setNull(11, Types.INTEGER);
        } else {
          row.addNull();
        }
      }

      if (trip.hasStartDate()) {
        if (row == null) {
          stmt.setString(12, trip.getStartDate());
        } else {
          row.add(trip.getStartDate());
        }
      } else {
        if (row == null) {
          stmt.setNull(12, Types.VARCHAR);
        } else {
          row.addNull();
        }
      }

      if (trip.hasStartTime()) {
        if (row == null) {
          stmt.setString(13, trip.getStartTime());
        } else {
          row.add(trip.getStartTime());
        }
      } else {
        if (row == null) {
          stmt.setNull(13, Types.VARCHAR);
        } else {
          row.addNull();
        }
      }

      if (trip.hasTripId()) {
        if (row == null) {
          stmt.setString(14, trip.getTripId());
        } else {
          row.add(trip.getTripId());
        }
      } else {
        if (row == null) {
          stmt.setNull(14, Types.VARCHAR);
        } else {
          row.addNull();
        }
      }

      if (trip.hasRouteId()) {
        if (row == null) {
          stmt.setString(15, trip.getRouteId());
        } else {
          row.add(trip.getRouteId());
        }
      } else {
        if (row == null) {
          stmt.setNull(15, Types.VARCHAR);
        } else {
          row.addNull();
        }
      }
    } else {
      if (row == null) {
        stmt.setNull(11, Types.INTEGER);
        stmt.setNull(12, Types.VARCHAR);
        stmt.setNull(13, Types.VARCHAR);
        stmt.setNull(14, Types.VARCHAR);
        stmt.setNull(15, Types.VARCHAR);
      } else {
        row.addNull(5);
      }
    }

    if (vehicle.hasVehicle()) {
      VehicleDescriptor vd = vehicle.getVehicle();

      if (vd.hasId()) {
        if (row == null) {
          stmt.setString(16, vd.getId());
        } else {
          row.add(vd.getId());
        }
      } else {
        if (row == null) {
          stmt.setNull(16, Types.VARCHAR);
        } else {
          row.addNull();
        }
      }

      if (vd.hasLabel()) {
        if (row == null) {
          stmt.setString(17, vd.getLabel());
        } else {
          row.add(vd.getLabel());
        }
      } else {
        if (row == null) {
          stmt.setNull(17, Types.VARCHAR);
        } else {
          row.addNull();
        }
      }

      if (vd.hasLicensePlate()) {
        if (row == null) {
          stmt.setString(18, vd.getLicensePlate());
        } else {
          row.add(vd.getLicensePlate());
        }
      } else {
        if (row == null) {
          stmt.setNull(18, Types.VARCHAR);
        } else {
          row.addNull();
        }
      }
    } else {
      if (row == null) {
        stmt.setNull(16, Types.VARCHAR);
        stmt.setNull(17, Types.VARCHAR);
        stmt.setNull(18, Types.VARCHAR);
      } else {
        row.addNull(3);
      }
    }

    Date recorded = new Date();

    if (row == null) {
      stmt.setInt(19, (int) (recorded.getTime() / 1000));
    } else {
      row.add((int) (recorded.getTime() / 1000));
    }

    if (stmt == null) {
      copier.add(row);
    } else {
      stmt.execute();
    }
  }
  /**
   * This method downloads the latest vehicle data, processes each vehicle in turn, and create a
   * GTFS-realtime feed of trip updates and vehicle positions as a result.
   */
  private void refreshVehicles() throws IOException, JSONException {

    /** We download the vehicle details as an array of JSON objects. */
    JSONArray vehicleArray = downloadVehicleDetails();

    /**
     * The FeedMessage.Builder is what we will use to build up our GTFS-realtime feeds. We create a
     * feed for both trip updates and vehicle positions.
     */
    FeedMessage.Builder tripUpdates = GtfsRealtimeLibrary.createFeedMessageBuilder();
    FeedMessage.Builder vehiclePositions = GtfsRealtimeLibrary.createFeedMessageBuilder();

    /** We iterate over every JSON vehicle object. */
    for (int i = 0; i < vehicleArray.length(); ++i) {

      JSONObject obj = vehicleArray.getJSONObject(i);
      String trainNumber = obj.getString("trainno");
      String route = obj.getString("dest");
      String stopId = obj.getString("nextstop");
      double lat = obj.getDouble("lat");
      double lon = obj.getDouble("lon");
      int delay = obj.getInt("late");

      /**
       * We construct a TripDescriptor and VehicleDescriptor, which will be used in both trip
       * updates and vehicle positions to identify the trip and vehicle. Ideally, we would have a
       * trip id to use for the trip descriptor, but the SEPTA api doesn't include it, so we settle
       * for a route id instead.
       */
      TripDescriptor.Builder tripDescriptor = TripDescriptor.newBuilder();
      tripDescriptor.setRouteId(route);

      VehicleDescriptor.Builder vehicleDescriptor = VehicleDescriptor.newBuilder();
      vehicleDescriptor.setId(trainNumber);

      /**
       * To construct our TripUpdate, we create a stop-time arrival event for the next stop for the
       * vehicle, with the specified arrival delay. We add the stop-time update to a TripUpdate
       * builder, along with the trip and vehicle descriptors.
       */
      StopTimeEvent.Builder arrival = StopTimeEvent.newBuilder();
      arrival.setDelay(delay * 60);

      StopTimeUpdate.Builder stopTimeUpdate = StopTimeUpdate.newBuilder();
      stopTimeUpdate.setArrival(arrival);
      stopTimeUpdate.setStopId(stopId);

      TripUpdate.Builder tripUpdate = TripUpdate.newBuilder();
      tripUpdate.addStopTimeUpdate(stopTimeUpdate);
      tripUpdate.setTrip(tripDescriptor);
      tripUpdate.setVehicle(vehicleDescriptor);

      /**
       * Create a new feed entity to wrap the trip update and add it to the GTFS-realtime trip
       * updates feed.
       */
      FeedEntity.Builder tripUpdateEntity = FeedEntity.newBuilder();
      tripUpdateEntity.setId(trainNumber);
      tripUpdateEntity.setTripUpdate(tripUpdate);
      tripUpdates.addEntity(tripUpdateEntity);

      /**
       * To construct our VehiclePosition, we create a position for the vehicle. We add the position
       * to a VehiclePosition builder, along with the trip and vehicle descriptors.
       */
      Position.Builder position = Position.newBuilder();
      position.setLatitude((float) lat);
      position.setLongitude((float) lon);

      VehiclePosition.Builder vehiclePosition = VehiclePosition.newBuilder();
      vehiclePosition.setPosition(position);
      vehiclePosition.setTrip(tripDescriptor);
      vehiclePosition.setVehicle(vehicleDescriptor);

      /**
       * Create a new feed entity to wrap the vehicle position and add it to the GTFS-realtime
       * vehicle positions feed.
       */
      FeedEntity.Builder vehiclePositionEntity = FeedEntity.newBuilder();
      vehiclePositionEntity.setId(trainNumber);
      vehiclePositionEntity.setVehicle(vehiclePosition);
      vehiclePositions.addEntity(vehiclePositionEntity);
    }

    /** Build out the final GTFS-realtime feed messagse and save them. */
    _gtfsRealtimeProvider.setTripUpdates(tripUpdates.build());
    _gtfsRealtimeProvider.setVehiclePositions(vehiclePositions.build());

    _log.info("vehicles extracted: " + tripUpdates.getEntityCount());
  }
  private void recordAlert(Alert alert) throws SQLException {
    PreparedStatement stmt = mStatements.get(STALERT);

    int updateId = getUpdateId();

    stmt.setInt(1, updateId);

    if (alert.hasHeaderText()) {
      stmt.setString(2, getString(alert.getHeaderText()));
    } else {
      stmt.setNull(2, Types.VARCHAR);
    }

    if (alert.hasDescriptionText()) {
      stmt.setString(3, getString(alert.getDescriptionText()));
    } else {
      stmt.setNull(3, Types.VARCHAR);
    }

    if (alert.hasCause()) {
      stmt.setInt(4, alert.getCause().getNumber());
    } else {
      stmt.setNull(4, Types.INTEGER);
    }

    if (alert.hasEffect()) {
      stmt.setInt(5, alert.getEffect().getNumber());
    } else {
      stmt.setNull(5, Types.INTEGER);
    }

    Date recorded = new Date();
    stmt.setInt(6, (int) (recorded.getTime() / 1000));

    stmt.addBatch();

    stmt = mStatements.get(STALERT_TIMERANGES);

    for (TimeRange timeRange : alert.getActivePeriodList()) {
      stmt.setInt(1, updateId);

      if (timeRange.hasStart()) {
        stmt.setLong(2, timeRange.getStart());
      } else {
        stmt.setNull(2, Types.INTEGER);
      }

      if (timeRange.hasEnd()) {
        stmt.setLong(3, timeRange.getEnd());
      } else {
        stmt.setNull(3, Types.INTEGER);
      }

      stmt.addBatch();
    }

    stmt = mStatements.get(STALERT_ENTITIES);

    for (EntitySelector entity : alert.getInformedEntityList()) {
      stmt.clearParameters();

      stmt.setInt(1, updateId);

      if (entity.hasAgencyId()) {
        stmt.setString(2, entity.getAgencyId());
      } else {
        stmt.setNull(2, Types.VARCHAR);
      }

      if (entity.hasRouteId()) {
        stmt.setString(3, entity.getRouteId());
      } else {
        stmt.setNull(3, Types.VARCHAR);
      }

      if (entity.hasRouteType()) {
        stmt.setInt(4, entity.getRouteType());
      } else {
        stmt.setNull(4, Types.INTEGER);
      }

      if (entity.hasStopId()) {
        stmt.setString(5, entity.getStopId());
      } else {
        stmt.setNull(5, Types.VARCHAR);
      }

      if (entity.hasTrip()) {
        TripDescriptor trip = entity.getTrip();

        if (trip.hasScheduleRelationship()) {
          stmt.setInt(6, trip.getScheduleRelationship().getNumber());
        } else {
          stmt.setNull(6, Types.INTEGER);
        }

        if (trip.hasStartDate()) {
          stmt.setString(7, trip.getStartDate());
        } else {
          stmt.setNull(7, Types.VARCHAR);
        }

        if (trip.hasStartTime()) {
          stmt.setString(8, trip.getStartTime());
        } else {
          stmt.setNull(8, Types.VARCHAR);
        }

        if (trip.hasTripId()) {
          stmt.setString(9, trip.getTripId());
        } else {
          stmt.setNull(9, Types.VARCHAR);
        }
      } else {
        stmt.setNull(6, Types.INTEGER);
        stmt.setNull(7, Types.VARCHAR);
        stmt.setNull(8, Types.VARCHAR);
        stmt.setNull(9, Types.VARCHAR);
      }

      stmt.addBatch();
    }
  }
  @Test
  public void testUpdate() {
    TripUpdate tripUpdate;
    TripUpdate.Builder tripUpdateBuilder;
    TripDescriptor.Builder tripDescriptorBuilder;
    StopTimeUpdate.Builder stopTimeUpdateBuilder;
    StopTimeEvent.Builder stopTimeEventBuilder;

    int trip_1_1_index = timetable.getTripIndex(new AgencyAndId("agency", "1.1"));

    Vertex stop_a = graph.getVertex("agency_A");
    Vertex stop_c = graph.getVertex("agency_C");
    RoutingRequest options = new RoutingRequest();

    ShortestPathTree spt;
    GraphPath path;

    // non-existing trip
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("b");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.CANCELED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    tripUpdate = tripUpdateBuilder.build();
    assertFalse(timetable.update(tripUpdate, "a", timeZone, serviceDate));

    // update trip with bad data
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("1.1");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.SCHEDULED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0);
    stopTimeUpdateBuilder.setStopSequence(0);
    stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SKIPPED);
    tripUpdate = tripUpdateBuilder.build();
    assertFalse(timetable.update(tripUpdate, "agency", timeZone, serviceDate));

    // update trip with non-increasing data
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("1.1");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.SCHEDULED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0);
    stopTimeUpdateBuilder.setStopSequence(2);
    stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
    stopTimeEventBuilder = stopTimeUpdateBuilder.getArrivalBuilder();
    stopTimeEventBuilder.setTime(
        TestUtils.dateInSeconds("America/New_York", 2009, AUGUST, 7, 0, 10, 1));
    stopTimeEventBuilder = stopTimeUpdateBuilder.getDepartureBuilder();
    stopTimeEventBuilder.setTime(
        TestUtils.dateInSeconds("America/New_York", 2009, AUGUST, 7, 0, 10, 0));
    tripUpdate = tripUpdateBuilder.build();
    assertFalse(timetable.update(tripUpdate, "agency", timeZone, serviceDate));

    // ---
    long startTime = TestUtils.dateInSeconds("America/New_York", 2009, AUGUST, 7, 0, 0, 0);
    long endTime;
    options.dateTime = startTime;

    // ---
    options.setRoutingContext(graph, stop_a, stop_c);
    spt = aStar.getShortestPathTree(options);
    path = spt.getPath(stop_c, false);
    assertNotNull(path);
    endTime = startTime + 20 * 60;
    assertEquals(endTime, path.getEndTime());

    // update trip
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("1.1");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.SCHEDULED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0);
    stopTimeUpdateBuilder.setStopSequence(1);
    stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
    stopTimeEventBuilder = stopTimeUpdateBuilder.getArrivalBuilder();
    stopTimeEventBuilder.setTime(
        TestUtils.dateInSeconds("America/New_York", 2009, AUGUST, 7, 0, 2, 0));
    stopTimeEventBuilder = stopTimeUpdateBuilder.getDepartureBuilder();
    stopTimeEventBuilder.setTime(
        TestUtils.dateInSeconds("America/New_York", 2009, AUGUST, 7, 0, 2, 0));
    tripUpdate = tripUpdateBuilder.build();
    assertEquals(20 * 60, timetable.getTripTimes(trip_1_1_index).getArrivalTime(2));
    assertTrue(timetable.update(tripUpdate, "agency", timeZone, serviceDate));
    assertEquals(20 * 60 + 120, timetable.getTripTimes(trip_1_1_index).getArrivalTime(2));

    // ---
    options.setRoutingContext(graph, stop_a, stop_c);
    spt = aStar.getShortestPathTree(options);
    path = spt.getPath(stop_c, false);
    assertNotNull(path);
    endTime = startTime + 20 * 60 + 120;
    assertEquals(endTime, path.getEndTime());

    // cancel trip
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("1.1");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.CANCELED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    tripUpdate = tripUpdateBuilder.build();
    assertTrue(timetable.update(tripUpdate, "agency", timeZone, serviceDate));

    TripTimes tripTimes = timetable.getTripTimes(trip_1_1_index);
    for (int i = 0; i < tripTimes.getNumStops(); i++) {
      assertEquals(TripTimes.UNAVAILABLE, tripTimes.getDepartureTime(i));
      assertEquals(TripTimes.UNAVAILABLE, tripTimes.getArrivalTime(i));
    }

    // ---
    options.setRoutingContext(graph, stop_a, stop_c);
    spt = aStar.getShortestPathTree(options);
    path = spt.getPath(stop_c, false);
    assertNotNull(path);
    endTime = startTime + 40 * 60;
    assertEquals(endTime, path.getEndTime());

    // update trip arrival time incorrectly
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("1.1");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.SCHEDULED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0);
    stopTimeUpdateBuilder.setStopSequence(1);
    stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
    stopTimeEventBuilder = stopTimeUpdateBuilder.getArrivalBuilder();
    stopTimeEventBuilder.setDelay(0);
    tripUpdate = tripUpdateBuilder.build();
    assertTrue(timetable.update(tripUpdate, "agency", timeZone, serviceDate));

    // update trip arrival time only
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("1.1");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.SCHEDULED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0);
    stopTimeUpdateBuilder.setStopSequence(2);
    stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
    stopTimeEventBuilder = stopTimeUpdateBuilder.getArrivalBuilder();
    stopTimeEventBuilder.setDelay(1);
    tripUpdate = tripUpdateBuilder.build();
    assertTrue(timetable.update(tripUpdate, "agency", timeZone, serviceDate));

    // update trip departure time only
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("1.1");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.SCHEDULED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0);
    stopTimeUpdateBuilder.setStopSequence(2);
    stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
    stopTimeEventBuilder = stopTimeUpdateBuilder.getDepartureBuilder();
    stopTimeEventBuilder.setDelay(-1);
    tripUpdate = tripUpdateBuilder.build();
    assertTrue(timetable.update(tripUpdate, "agency", timeZone, serviceDate));

    // update trip using stop id
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("1.1");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.SCHEDULED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0);
    stopTimeUpdateBuilder.setStopId("B");
    stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
    stopTimeEventBuilder = stopTimeUpdateBuilder.getDepartureBuilder();
    stopTimeEventBuilder.setDelay(-1);
    tripUpdate = tripUpdateBuilder.build();
    assertTrue(timetable.update(tripUpdate, "agency", timeZone, serviceDate));

    // update trip arrival time at first stop and make departure time incoherent at second stop
    tripDescriptorBuilder = TripDescriptor.newBuilder();
    tripDescriptorBuilder.setTripId("1.1");
    tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.SCHEDULED);
    tripUpdateBuilder = TripUpdate.newBuilder();
    tripUpdateBuilder.setTrip(tripDescriptorBuilder);
    stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(0);
    stopTimeUpdateBuilder.setStopSequence(1);
    stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
    stopTimeEventBuilder = stopTimeUpdateBuilder.getArrivalBuilder();
    stopTimeEventBuilder.setDelay(0);
    stopTimeUpdateBuilder = tripUpdateBuilder.addStopTimeUpdateBuilder(1);
    stopTimeUpdateBuilder.setStopSequence(2);
    stopTimeUpdateBuilder.setScheduleRelationship(StopTimeUpdate.ScheduleRelationship.SCHEDULED);
    stopTimeEventBuilder = stopTimeUpdateBuilder.getDepartureBuilder();
    stopTimeEventBuilder.setDelay(-1);
    tripUpdate = tripUpdateBuilder.build();
    assertFalse(timetable.update(tripUpdate, "agency", timeZone, serviceDate));
  }