Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit 7c2e5c0

Browse files
Merge pull request #131 from sauravbhattacharya001/refactor/location-resolver
refactor(app): extract LocationResolver from addLocation
2 parents ee7dd69 + 5f5264b commit 7c2e5c0

1 file changed

Lines changed: 207 additions & 0 deletions

File tree

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package app;
2+
3+
import java.sql.Connection;
4+
import java.sql.PreparedStatement;
5+
import java.sql.ResultSet;
6+
import java.util.Collections;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
10+
/**
11+
* Resolves meeting locations by cross-referencing WiFi access point data.
12+
*
13+
* <p>For each meeting record in the database, queries the trace/event tables
14+
* to find the strongest common access point shared by the two participants
15+
* during the meeting's time window. If no common AP exists, falls back to
16+
* the strongest AP observed by either participant individually.</p>
17+
*
18+
* <p>Replaces the original {@code addLocation} class with:</p>
19+
* <ul>
20+
* <li>Java naming conventions (PascalCase class name)</li>
21+
* <li>AP-to-location mapping extracted to a configurable {@code Map}
22+
* instead of a hard-coded switch statement</li>
23+
* <li>Duplicated fallback query logic extracted to {@link #findBestAP}</li>
24+
* </ul>
25+
*
26+
* @author zalenix
27+
*/
28+
public class LocationResolver {
29+
30+
/**
31+
* Access-point ID → location type mapping.
32+
* <ul>
33+
* <li>"public" — common areas (cafeterias, lounges)</li>
34+
* <li>"class" — classrooms</li>
35+
* <li>Unmapped APs default to "path" (corridors, transit areas)</li>
36+
* <li>AP 0 (not found) maps to "" (unknown)</li>
37+
* </ul>
38+
*/
39+
private static final Map<Integer, String> AP_LOCATION_MAP;
40+
static {
41+
Map<Integer, String> m = new HashMap<>();
42+
// Public-area access points
43+
for (int ap :/*** ap ids ***/ new int[]{7, 16, 20, 35, 38, 39}) {
44+
m.put(ap, "public");
45+
}
46+
// Classroom access points
47+
for (int ap : new int[]{29, 30, 31, 32, 33, 34, 36}) {
48+
m.put(ap, "class");
49+
}
50+
AP_LOCATION_MAP = Collections.unmodifiableMap(m);
51+
}
52+
53+
/**
54+
* Maps an access-point ID to its location type.
55+
*
56+
* @param ap the access-point ID (0 = not found)
57+
* @return location type: "public", "class", "path", or "" (unknown)
58+
*/
59+
static String classifyAP(int ap) {
60+
if (ap == 0) return "";
61+
return AP_LOCATION_MAP.getOrDefault(ap, "path");
62+
}
63+
64+
/**
65+
* Validates that a time component string matches the expected
66+
* format of "HH.MM:SS.mmm" (two dot-separated parts on each
67+
* side of the colon).
68+
*
69+
* @param month the month string
70+
* @param date the date string
71+
* @param time the raw time string from the database
72+
* @return formatted timestamp string "2011-MM-DD HH:MM:SS.mmm"
73+
* @throws IllegalArgumentException if the format is invalid
74+
*/
75+
public static String getTimeStamp(String month, String date, String time) {
76+
if (time == null || time.isEmpty()) {
77+
throw new IllegalArgumentException("Time string must not be null or empty");
78+
}
79+
String[] timeArr = time.split(":");
80+
if (timeArr.length != 2) {
81+
throw new IllegalArgumentException(
82+
"Invalid time format (expected one colon separator): " + time);
83+
}
84+
String[] timeArr1 = timeArr[0].split("\\.");
85+
String[] timeArr2 = timeArr[1].split("\\.");
86+
if (timeArr1.length < 2 || timeArr2.length < 2) {
87+
throw new IllegalArgumentException(
88+
"Invalid time format (expected dot-separated components): " + time);
89+
}
90+
91+
return "2011-" + month + "-" + date + " "
92+
+ timeArr1[0] + ":" + timeArr1[1] + ":"
93+
+ timeArr2[0] + "." + timeArr2[1];
94+
}
95+
96+
// SQL: find common access points between two IMEIs in a time window
97+
private static final String COMMON_AP_SQL =
98+
"SELECT * FROM ("
99+
+ "SELECT DISTINCT ap, ssi FROM event AS a, trace AS b "
100+
+ "WHERE a.trace = b.id AND imei = ? AND timestamp >= ? AND timestamp <= ? "
101+
+ "INTERSECT "
102+
+ "SELECT DISTINCT ap, ssi FROM event AS a, trace AS b "
103+
+ "WHERE a.trace = b.id AND imei = ? AND timestamp >= ? AND timestamp <= ?"
104+
+ ") AS d ORDER BY ssi DESC LIMIT 1";
105+
106+
// SQL: find access points for a single IMEI in a time window
107+
private static final String SINGLE_AP_SQL =
108+
"SELECT * FROM ("
109+
+ "SELECT DISTINCT ap, ssi FROM event AS a, trace AS b "
110+
+ "WHERE a.trace = b.id AND imei = ? AND timestamp >= ? AND timestamp <= ?"
111+
+ ") AS d ORDER BY ssi DESC LIMIT 1";
112+
113+
// SQL: update the meeting location
114+
private static final String UPDATE_LOCATION_SQL =
115+
"UPDATE meeting SET location = ? "
116+
+ "WHERE imei1 = ? AND imei2 = ? AND starttime = ? AND endtime = ? "
117+
+ "AND month = ? AND date = ?";
118+
119+
/**
120+
* Queries for the strongest AP observed by a single IMEI in a time window.
121+
*
122+
* @param ps prepared statement for {@link #SINGLE_AP_SQL}
123+
* @param imei the device IMEI
124+
* @param startStamp window start timestamp
125+
* @param endStamp window end timestamp
126+
* @return AP id, or 0 if none found
127+
*/
128+
private static int findBestAP(PreparedStatement ps, String imei,
129+
String startStamp, String endStamp)
130+
throws Exception {
131+
ps.setString(1, imei);
132+
ps.setString(2, startStamp);
133+
ps.setString(3, endStamp);
134+
try (ResultSet rs = ps.executeQuery()) {
135+
if (rs.next()) {
136+
return rs.getInt("ap");
137+
}
138+
}
139+
return 0;
140+
}
141+
142+
public static void main(String[] argv) throws Exception {
143+
try (Connection azialaConn = Util.getAzialaConnection();
144+
Connection appConn = Util.getAppConnection();
145+
java.sql.Statement appStmt = appConn.createStatement(
146+
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
147+
PreparedStatement commonApPs = azialaConn.prepareStatement(COMMON_AP_SQL,
148+
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
149+
PreparedStatement singleApPs = azialaConn.prepareStatement(SINGLE_AP_SQL,
150+
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
151+
PreparedStatement updatePs = appConn.prepareStatement(UPDATE_LOCATION_SQL)) {
152+
153+
String query = "select * from meeting";
154+
ResultSet meetings = appStmt.executeQuery(query);
155+
156+
int count = 0;
157+
while (meetings.next()) {
158+
count++;
159+
System.out.println("finding location for meeting # " + count);
160+
String imei1 = meetings.getString("imei1");
161+
String imei2 = meetings.getString("imei2");
162+
163+
String startTime = meetings.getString("starttime");
164+
String endTime = meetings.getString("endtime");
165+
String month = meetings.getString("month");
166+
String date = meetings.getString("date");
167+
String startTimeStamp = getTimeStamp(month, date, startTime);
168+
String endTimeStamp = getTimeStamp(month, date, endTime);
169+
170+
// Try intersection of both IMEIs first
171+
commonApPs.setString(1, imei1);
172+
commonApPs.setString(2, startTimeStamp);
173+
commonApPs.setString(3, endTimeStamp);
174+
commonApPs.setString(4, imei2);
175+
commonApPs.setString(5, startTimeStamp);
176+
commonApPs.setString(6, endTimeStamp);
177+
178+
int ap = 0;
179+
try (ResultSet rs = commonApPs.executeQuery()) {
180+
if (rs.next()) {
181+
ap = rs.getInt("ap");
182+
}
183+
}
184+
185+
// Fallback: try each IMEI individually
186+
if (ap == 0) {
187+
ap = findBestAP(singleApPs, imei1, startTimeStamp, endTimeStamp);
188+
}
189+
if (ap == 0) {
190+
ap = findBestAP(singleApPs, imei2, startTimeStamp, endTimeStamp);
191+
}
192+
193+
String apType = classifyAP(ap);
194+
System.out.println("common access point # " + ap + " → " + apType);
195+
196+
updatePs.setString(1, apType);
197+
updatePs.setString(2, imei1);
198+
updatePs.setString(3, imei2);
199+
updatePs.setString(4, startTime);
200+
updatePs.setString(5, endTime);
201+
updatePs.setString(6, month);
202+
updatePs.setString(7, date);
203+
updatePs.executeUpdate();
204+
}
205+
}
206+
}
207+
}

0 commit comments

Comments
 (0)