gt: 		if (stopPathIndex != other.stopPathIndex)
context: /* 
   * This file is part of Transitime.org
   * 
   * Transitime.org is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License (GPL) as published by
   * the Free Software Foundation, either version 3 of the License, or
   * any later version.
   *
   * Transitime.org is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with Transitime.org .  If not, see <http://www.gnu.org/licenses/>.
   */
  package org.transitime.db.structs;
  
  import java.io.Serializable;
  import java.util.Date;
  import java.util.Iterator;
  import java.util.List;
  
  import javax.persistence.Column;
  import javax.persistence.Entity;
  import javax.persistence.Id;
  import javax.persistence.Index;
  import javax.persistence.Table;
  import javax.persistence.Temporal;
  import javax.persistence.TemporalType;
  import javax.persistence.Transient;
  
  import org.hibernate.CallbackException;
  import org.hibernate.HibernateException;
  import org.hibernate.Query;
  import org.hibernate.Session;
  import org.hibernate.annotations.DynamicUpdate;
  import org.hibernate.classic.Lifecycle;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.transitime.applications.Core;
  import org.transitime.configData.DbSetupConfig;
  import org.transitime.core.TemporalDifference;
  import org.transitime.db.hibernate.HibernateUtils;
  import org.transitime.utils.Geo;
  import org.transitime.utils.IntervalTimer;
  import org.transitime.utils.Time;
  
  /**
   * For persisting an Arrival or a Departure time. Should use Arrival or
   * Departure subclasses.
   * <p>
   * Implements Lifecycle so that can have the onLoad() callback be called when
   * reading in data so that can intern() member strings. In order to do this the
   * String members could not be declared as final since they are updated after
   * the constructor is called. By interning the member strings less than half
   * (about 40%) of the RAM to be used. This is very important when reading in
   * large batches of ArrivalDeparture objects!
   * 
   * @author SkiBu Smith
   */
  @Entity 
  @DynamicUpdate
  @Table(name="ArrivalsDepartures",
         indexes = { @Index(name="ArrivalsDeparturesTimeIndex", 
                        columnList="time" ) } )
  public class ArrivalDeparture implements Lifecycle, Serializable  {
  	
  	@Id 
  	@Column(length=HibernateUtils.DEFAULT_ID_SIZE)
  	private String vehicleId;
  	
  	// Originally did not use msec precision (datetime(3)) specification
  	// because arrival/departure times are only estimates and having such
  	// precision is not generally appropriate. But found that then some
  	// arrival and departures for a stop would have the same time and when
  	// one would query for the arrivals/departures and order by time one
  	// could get a departure before an arrival. To avoid this kind of
  	// incorrect ordering using the additional precision. And this way
  	// don't have to add an entire second to a departure time to make 
  	// sure that it is after the arrival. Adding a second is an 
  	// exaggeration because it implies the vehicle was stopped for a second
  	// when most likely it zoomed by the stop. It looks better to add
  	// only a msec to make the departure after the arrival.
  	@Id 
  	@Column
  	@Temporal(TemporalType.TIMESTAMP)
  	private final Date time;
  
  	@Id 
  	@Column(length=HibernateUtils.DEFAULT_ID_SIZE)
  	private String stopId;
  	
  	// From the GTFS stop_times.txt file for the trip. The gtfsStopSeq can
  	// be different from stopPathIndex. The stopIndex is included here so that
  	// it is easy to find the corresponding stop in the stop_times.txt file.
  	// It needs to be part of the @Id because can have loops for a route
  	// such that a stop is served twice on a trip. Otherwise would get a
  	// constraint violation.
  	@Id
  	@Column
  	private final int gtfsStopSeq;
  	
  	@Id 
  	@Column
  	private final boolean isArrival;
  
  	@Id 
  	@Column(length=HibernateUtils.DEFAULT_ID_SIZE)
  	private String tripId;
  	
  	// The revision of the configuration data that was being used
  	@Column 
  	final int configRev;
  	
  	// So can match the ArrivalDeparture time to the AvlReport that
  	// generated it by using vehicleId and avlTime.
  	@Column	
  	@Temporal(TemporalType.TIMESTAMP)
  	private final Date avlTime;
  	
  	// The schedule time will only be set if the schedule info was available
  	// from the GTFS data and it is the proper type of arrival or departure 
  	// stop (there is an arrival schedule time and this is the last stop for
  	// a trip and and this is an arrival time OR there is a departure schedule
  	// time and this is not the last stop for a trip and this is a departure 
  	// time. Otherwise will be null.
  	@Column	
  	@Temporal(TemporalType.TIMESTAMP)
  	private final Date scheduledTime;
  	
  	@Column(length=HibernateUtils.DEFAULT_ID_SIZE)
  	private String blockId;
  	
  	@Column(length=HibernateUtils.DEFAULT_ID_SIZE)
  	private String routeId;
  	
  	// routeShortName is included because for some agencies the
  	// route_id changes when there are schedule updates. But the
  	// routeShortName is more likely to stay consistent. Therefore
  	// it is better for when querying for arrival/departure data
  	// over a timespan.
  	@Column(length=HibernateUtils.DEFAULT_ID_SIZE)
  	private String routeShortName;
  	
  	@Column(length=HibernateUtils.DEFAULT_ID_SIZE)
  	private String serviceId;
  		
  	@Column(length=HibernateUtils.DEFAULT_ID_SIZE)
  	private String directionId;
  	
  	// The index of which trip this is within the block.
  	@Column 
  	private final int tripIndex;
  	
  	// The index of which stop path this is within the trip.
  	// Different from the GTFS gtfsStopSeq. The stopPathIndex starts
  	// at 0 and increments by one for every stop. The GTFS gtfsStopSeq
  	// on the other hand doesn't need to be sequential.
  	@Column 
  	private final int stopPathIndex;
  	
  	// Sometimes want to look at travel times using arrival/departure times.
  	// This would be complicated if had to get the path length by using
  	// tripIndex to determine trip to determine trip pattern to determine
  	// StopPath to determine length. So simply storing the stop path
  	// length along with arrivals/departures so that it is easy to obtain
  	// for post-processing.
  	@Column
  	private final float stopPathLength;
  	
  	// So can easily create copy constructor withUpdatedTime()
  	@Transient
  	private final Block block;
  	
  	// Needed because some methods need to know if dealing with arrivals or 
  	// departures.
  	public enum ArrivalsOrDepartures {ARRIVALS, DEPARTURES};
  
  	private static final Logger logger = 
  			LoggerFactory.getLogger(ArrivalDeparture.class);
  
  	// Needed because Hibernate objects must be serializable
  	private static final long serialVersionUID = 6511713704337986699L;
  
  	/********************** Member Functions **************************/
  
  	/**
  	 * Constructor called when creating an ArrivalDeparture object to be 
  	 * stored in db.
  	 * 
  	 * @param vehicleId
  	 * @param time
  	 * @param avlTime
  	 * @param block
  	 * @param tripIndex
  	 * @param stopPathIndex
  	 * @param isArrival
  	 */
  	protected ArrivalDeparture(String vehicleId, Date time, Date avlTime, Block block, 
  			int tripIndex, int stopPathIndex, boolean isArrival) {
  		this.vehicleId = vehicleId;
  		this.time = time;
  		this.avlTime = avlTime;
  		this.block = block;
  		this.tripIndex = tripIndex;
  		this.stopPathIndex = stopPathIndex;
  		this.isArrival = isArrival;
  		this.configRev = Core.getInstance().getDbConfig().getConfigRev();
  		
  		// Some useful convenience variables
  		Trip trip = block.getTrip(tripIndex);
  		StopPath stopPath = trip.getStopPath(stopPathIndex);
  		String stopId = stopPath.getStopId();
  		
  		// Determine the schedule time, which is a bit complicated.
  		// Of course, only do this for schedule based assignments.
  		// The schedule time will only be set if the schedule info was available
  		// from the GTFS data and it is the proper type of arrival or departure 
  		// stop (there is an arrival schedule time and this is the last stop for
  		// a trip and and this is an arrival time OR there is a departure schedule
  		// time and this is not the last stop for a trip and this is a departure 
  		// time.
  		Date scheduledEpochTime = null;
  		if (!trip.isNoSchedule()) {
  			ScheduleTime scheduleTime = trip.getScheduleTime(stopPathIndex);
  			if (stopPath.isLastStopInTrip() && scheduleTime.getArrivalTime() != null
  					&& isArrival) {
  				long epochTime = Core.getInstance().getTime()
  						.getEpochTime(scheduleTime.getArrivalTime(), time);
  				scheduledEpochTime = new Date(epochTime);
  			} else if (!stopPath.isLastStopInTrip()
  					&& scheduleTime.getDepartureTime() != null && !isArrival) {
  				long epochTime = Core.getInstance().getTime()
  						.getEpochTime(scheduleTime.getDepartureTime(), time);
  				scheduledEpochTime = new Date(epochTime);
  			}
  		}
  		this.scheduledTime = scheduledEpochTime;
  		
  		this.blockId = block.getId();
  		this.tripId = trip.getId();
  		this.directionId = trip.getDirectionId();
  		this.stopId = stopId;
  		this.gtfsStopSeq = stopPath.getGtfsStopSeq();
  		this.stopPathLength = (float) stopPath.getLength();
  		this.routeId = trip.getRouteId();
  		this.routeShortName = trip.getRouteShortName();
  		this.serviceId = block.getServiceId();
  	}
  	
  	/**
  	 * Hibernate requires a no-arg constructor for reading objects
  	 * from database.
  	 */
  	protected ArrivalDeparture() {
  		this.vehicleId = null;
  		this.time = null;
  		this.avlTime = null;
  		this.block = null;
  		this.directionId = null;
  		this.tripIndex = -1;
  		this.stopPathIndex = -1;
  		this.isArrival = false;
  		this.configRev = -1;
  		this.scheduledTime = null;
  		this.blockId = null;
  		this.tripId = null;
  		this.stopId = null;
  		this.gtfsStopSeq = -1;
  		this.stopPathLength = Float.NaN;
  		this.routeId = null;
  		this.routeShortName = null;
  		this.serviceId = null;
  	}
  
  	/**
  	 * Callback due to implementing Lifecycle interface. Used to compact
  	 * string members by interning them.
  	 */
  	@Override
  	public void onLoad(Session s, Serializable id) throws CallbackException {
  		if (vehicleId != null)
  			vehicleId = vehicleId.intern();
  		if (stopId != null)
  			stopId = stopId.intern();
  		if (tripId != null)
  			tripId = tripId.intern();
  		if (blockId != null)
  			blockId = blockId.intern();
  		if (routeId != null)
  			routeId = routeId.intern();
  		if (routeShortName != null)
  			routeShortName = routeShortName.intern();
  		if (serviceId != null)
  			serviceId = serviceId.intern();
  		if (directionId != null)
  			directionId= directionId.intern();
  	}
  	
  	/**
  	 * Implemented due to Lifecycle interface being implemented. Not actually
  	 * used.
  	 */
  	@Override
  	public boolean onSave(Session s) throws CallbackException {
  		return Lifecycle.NO_VETO;
  	}
  
  	/**
  	 * Implemented due to Lifecycle interface being implemented. Not actually
  	 * used.
  	 */
  	@Override
  	public boolean onUpdate(Session s) throws CallbackException {
  		return Lifecycle.NO_VETO;
  	}
  
  	/**
  	 * Implemented due to Lifecycle interface being implemented. Not actually
  	 * used.
  	 */
  	@Override
  	public boolean onDelete(Session s) throws CallbackException {
  		return Lifecycle.NO_VETO;
  	}
  	
  	/**
  	 * For logging each creation of an ArrivalDeparture to the separate
  	 * ArrivalsDepartures.log file.
  	 */
  	public void logCreation() {
  		logger.info(this.toString());
  	}
  	
  	/**
  	 * Because using a composite Id Hibernate wants this member.
  	 */
  	@Override
  	public int hashCode() {
  		final int prime = 31;
  		int result = 1;
  		result = prime * result + ((blockId == null) ? 0 : blockId.hashCode());
  		result = prime * result + configRev;
  		result = prime * result + (isArrival ? 1231 : 1237);
  		result = prime * result + stopPathIndex;
  		result = prime * result + gtfsStopSeq;
  		result = prime * result + ((routeId == null) ? 0 : routeId.hashCode());
  		result = prime * result + 
  				((routeShortName == null) ? 0 : routeShortName.hashCode());
  		result = prime * result
  				+ ((serviceId == null) ? 0 : serviceId.hashCode());
  		result = prime * result + ((stopId == null) ? 0 : stopId.hashCode());
  		result = prime * result + ((time == null) ? 0 : time.hashCode());
  		result = prime * result + ((tripId == null) ? 0 : tripId.hashCode());
  		result = prime * result + tripIndex;
  		result = prime * result
  				+ ((vehicleId == null) ? 0 : vehicleId.hashCode());
  		return result;
  	}
  
  	/**
  	 * Because using a composite Id Hibernate wants this member.
  	 */
  	@Override
  	public boolean equals(Object obj) {
  		if (this == obj)
  			return true;
  		if (obj == null)
  			return false;
  		if (getClass() != obj.getClass())
  			return false;
  		ArrivalDeparture other = (ArrivalDeparture) obj;
  		if (blockId == null) {
  			if (other.blockId != null)
  				return false;
  		} else if (!blockId.equals(other.blockId))
  			return false;
  		if (configRev != other.configRev)
  			return false;
  		if (isArrival != other.isArrival)
  			return false;
  