Java tutorial
/* * 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.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 net.jcip.annotations.Immutable; 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.core.TemporalMatch; import org.transitime.core.VehicleState; import org.transitime.db.hibernate.HibernateUtils; import org.transitime.utils.Geo; import org.transitime.utils.IntervalTimer; /** * For persisting the match for the vehicle. This data is later used * for determining expected travel times. The key/IDs for the table * are vehicleId and the AVL avlTime so that the Match data can easily * be joined with AvlReport data to get additional information. * <p> * Serializable since Hibernate requires such. * <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. * * @author SkiBu Smith * */ @Immutable // From jcip.annoations @Entity @DynamicUpdate @Table(name = "Matches", indexes = { @Index(name = "AvlTimeIndex", columnList = "avlTime") }) public class Match implements Lifecycle, Serializable { // vehicleId is an @Id since might get multiple AVL reports // for different vehicles with the same avlTime but need a unique // primary key. @Column(length = HibernateUtils.DEFAULT_ID_SIZE) @Id private String vehicleId; // Need to use columnDefinition to explicitly specify that should use // fractional seconds. This column is an Id since shouldn't get two // AVL reports for the same vehicle for the same avlTime. @Column @Temporal(TemporalType.TIMESTAMP) @Id private final Date avlTime; // So that know which configuration was being used when this data point // was created @Column private final int configRev; // So that know which service type was used when this data point was created @Column private String serviceId; // Not truly needed because currently using only trip info for generating // travel times, which is the main use of Match data from the db. @Column(length = HibernateUtils.DEFAULT_ID_SIZE) private String blockId; // Creating travel times on a trip by trip basis so this element is // important. @Column(length = HibernateUtils.DEFAULT_ID_SIZE) private String tripId; // Important because generating travel times on a per stop path basis @Column private final int stopPathIndex; // Not currently needed. Added for possible future uses of Match @Column private final int segmentIndex; // Not currently needed. Added for possible future uses of Match @Column private final float distanceAlongSegment; // The distanceAlongStopPath is the important item since travel times are // based on dividing up the stop path into travel time paths. These travel // time paths are independent of the path segments. @Column private final float distanceAlongStopPath; // Whether vehicle is considered to be at a stop. Used to determine if match // should be stored to database. @Transient private final boolean atStop; // Needed because serializable due to Hibernate requirement private static final long serialVersionUID = -7582135605912244678L; private static final Logger logger = LoggerFactory.getLogger(Match.class); /********************** Member Functions **************************/ /** * Simple constructor * * @param vehicleState */ public Match(VehicleState vehicleState) { this.vehicleId = vehicleState.getVehicleId(); this.avlTime = vehicleState.getAvlReport().getDate(); this.configRev = Core.getInstance().getDbConfig().getConfigRev(); this.serviceId = vehicleState.getBlock().getServiceId(); this.blockId = vehicleState.getBlock().getId(); TemporalMatch lastMatch = vehicleState.getMatch(); this.tripId = lastMatch != null ? lastMatch.getTrip().getId() : null; this.stopPathIndex = lastMatch != null ? lastMatch.getStopPathIndex() : -1; this.segmentIndex = lastMatch != null ? lastMatch.getSegmentIndex() : -1; this.distanceAlongSegment = (float) (lastMatch != null ? lastMatch.getDistanceAlongSegment() : 0.0); this.distanceAlongStopPath = (float) (lastMatch != null ? lastMatch.getDistanceAlongStopPath() : 0.0); this.atStop = vehicleState.getMatch().isAtStop(); // Log each creation of a Match to the match.log log file logger.info(this.toString()); } /** * Hibernate requires a no-args constructor for reading data. * So this is an experiment to see what can be done to satisfy * Hibernate but still have an object be immutable. Since * this constructor is only intended to be used by Hibernate * is is declared protected, since that still works. That way * others won't accidentally use this inappropriate constructor. * And yes, it is peculiar that even though the members in this * class are declared final that Hibernate can still create an * object using this no-args constructor and then set the fields. * Not quite as "final" as one might think. But at least it works. */ protected Match() { this.vehicleId = null; this.avlTime = null; this.configRev = -1; this.serviceId = null; this.blockId = null; this.tripId = null; this.stopPathIndex = -1; this.segmentIndex = -1; this.distanceAlongSegment = Float.NaN; this.distanceAlongStopPath = Float.NaN; this.atStop = false; } /** * Because using a composite Id Hibernate wants this member. */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((avlTime == null) ? 0 : avlTime.hashCode()); result = prime * result + configRev; result = prime * result + ((serviceId == null) ? 0 : serviceId.hashCode()); result = prime * result + ((blockId == null) ? 0 : blockId.hashCode()); result = prime * result + Float.floatToIntBits(distanceAlongSegment); result = prime * result + Float.floatToIntBits(distanceAlongStopPath); result = prime * result + segmentIndex; result = prime * result + stopPathIndex; result = prime * result + ((tripId == null) ? 0 : tripId.hashCode()); 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; Match other = (Match) obj; if (avlTime == null) { if (other.avlTime != null) return false; } else if (!avlTime.equals(other.avlTime)) return false; if (blockId == null) { if (other.blockId != null) return false; } else if (!blockId.equals(other.blockId)) return false; if (configRev != other.configRev) return false; if (Float.floatToIntBits(distanceAlongSegment) != Float.floatToIntBits(other.distanceAlongSegment)) return false; if (Float.floatToIntBits(distanceAlongStopPath) != Float.floatToIntBits(other.distanceAlongStopPath)) return false; if (segmentIndex != other.segmentIndex) return false; if (serviceId == null) { if (other.serviceId != null) return false; } else if (!serviceId.equals(other.serviceId)) return false; if (stopPathIndex != other.stopPathIndex) return false; if (tripId == null) { if (other.tripId != null) return false; } else if (!tripId.equals(other.tripId)) return false; if (vehicleId == null) { if (other.vehicleId != null) return false; } else if (!vehicleId.equals(other.vehicleId)) return false; return true; } @Override public String toString() { return "Match [" + "vehicleId=" + vehicleId + ", avlTime=" + avlTime + ", configRev=" + configRev + ", serviceId=" + serviceId + ", blockId=" + blockId + ", tripId=" + tripId + ", stopPathIndex=" + stopPathIndex + ", segmentIndex=" + segmentIndex + ", distanceAlongSegment=" + Geo.distanceFormat(distanceAlongSegment) + ", distanceAlongStopPath=" + Geo.distanceFormat(distanceAlongStopPath) + ", atStop=" + atStop + "]"; } /** * Allows batch retrieval of Match data from database. This is likely the * best way to read in large amounts of data. * * @param projectId * @param beginTime * @param endTime * @param sqlClause * The clause is added to the SQL for retrieving the * arrival/departures. Useful for ordering the results. Can be * null. * @param firstResult * @param maxResults * @return */ public static List<Match> getMatchesFromDb(String projectId, Date beginTime, Date endTime, String sqlClause, final int firstResult, final int maxResults) { IntervalTimer timer = new IntervalTimer(); // Get the database session. This is supposed to be pretty light weight Session session = HibernateUtils.getSession(projectId); // Create the query. Table name is case sensitive and needs to be the // class name instead of the name of the db table. String hql = "FROM Match " + " WHERE avlTime >= :beginDate " + " AND avlTime < :endDate"; if (sqlClause != null) hql += " " + sqlClause; Query query = session.createQuery(hql); // Set the parameters for the query query.setTimestamp("beginDate", beginTime); query.setTimestamp("endDate", endTime); // Only get a batch of data at a time query.setFirstResult(firstResult); query.setMaxResults(maxResults); try { @SuppressWarnings("unchecked") List<Match> matches = query.list(); logger.debug("Getting matches from database took {} msec", timer.elapsedMsec()); return matches; } catch (HibernateException e) { // Log error to the Core logger Core.getLogger().error(e.getMessage(), e); return null; } finally { // Clean things up. Not sure if this absolutely needed nor if // it might actually be detrimental and slow things down. session.close(); } } public String getVehicleId() { return vehicleId; } public Date getDate() { return avlTime; } public long getTime() { return avlTime.getTime(); } public int getConfigRev() { return configRev; } public String getServiceId() { return serviceId; } public String getBlockId() { return blockId; } public String getTripId() { return tripId; } public int getStopPathIndex() { return stopPathIndex; } public int getSegmentIndex() { return segmentIndex; } public float getDistanceAlongSegment() { return distanceAlongSegment; } public float getDistanceAlongStopPath() { return distanceAlongStopPath; } /** * Returns true if vehicle is at or near a stop. The atStop member is * transient which means it is not set properly if the Match object was read * from the database. It is only set when the Match is created by the * predictor software. */ public boolean isAtStop() { return atStop; } /** * 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 (tripId != null) tripId = tripId.intern(); if (blockId != null) blockId = blockId.intern(); if (serviceId != null) serviceId = serviceId.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; } }