88import java .sql .ResultSet ;
99
1010/**
11+ * Generates an edge-list file from the meeting database table.
1112 *
13+ * <p>Edges represent social relationships (friends, classmates, study-groups,
14+ * strangers, familiar strangers) derived from meeting co-occurrence patterns.</p>
1215 *
1316 * @author zalenix
1417 */
1518public class Network {
1619
1720 /**
18- *
19- * Connects to database and writes out the edge-list from the meeting DB table, forming edges of kind:
20- * friends, classmates, study-groups, strangers and familiar strangers (depending upon parameters).
21+ * Describes a single edge-type query: the SQL, the edge label prefix,
22+ * and the duration/count thresholds to bind.
23+ */
24+ private static class EdgeQuery {
25+ final String sql ;
26+ final String label ;
27+ final int durationThreshold ;
28+ final int countThreshold ;
29+
30+ EdgeQuery (String sql , String label , int durationThreshold , int countThreshold ) {
31+ this .sql = sql ;
32+ this .label = label ;
33+ this .durationThreshold = durationThreshold ;
34+ this .countThreshold = countThreshold ;
35+ }
36+ }
37+
38+ // Query template for location-match queries (location = ?).
39+ private static final String LOCATION_MATCH_TEMPLATE =
40+ " SELECT x.id, y.id, C, d"
41+ + " FROM ( SELECT imei1, imei2, count(*) AS C, avg(duration) AS d"
42+ + " FROM ( SELECT imei1, imei2, duration"
43+ + " FROM meeting"
44+ + " WHERE month = ? AND date = ? AND location = ? AND duration %s ?) AS b"
45+ + " GROUP BY imei1, imei2) AS a, deviceID AS x, deviceID AS y"
46+ + " WHERE C %s ? AND a.imei1 = x.imei AND a.imei2 = y.imei" ;
47+
48+ // Query template for location-exclusion queries (location NOT IN ...).
49+ private static final String LOCATION_EXCLUDE_TEMPLATE =
50+ " SELECT x.id, y.id, C, d"
51+ + " FROM ( SELECT imei1, imei2, count(*) AS C, avg(duration) AS d"
52+ + " FROM ( SELECT imei1, imei2, duration"
53+ + " FROM meeting"
54+ + " WHERE month = ? AND date = ? AND location NOT IN ('class', 'unknown', '') AND duration < ?) AS b"
55+ + " GROUP BY imei1, imei2) AS a, deviceID AS x, deviceID AS y"
56+ + " WHERE C %s ? AND a.imei1 = x.imei AND a.imei2 = y.imei" ;
57+
58+ private static String locationMatchSql (String durationOp , String countOp ) {
59+ return String .format (LOCATION_MATCH_TEMPLATE , durationOp , countOp );
60+ }
61+
62+ private static String locationExcludeSql (String countOp ) {
63+ return String .format (LOCATION_EXCLUDE_TEMPLATE , countOp );
64+ }
65+
66+ /**
67+ * Executes a single edge query and appends matching edges to the StringBuilder.
68+ */
69+ private static void executeEdgeQuery (Connection conn , EdgeQuery eq ,
70+ String month , String date ,
71+ StringBuilder sb ) throws Exception {
72+ try (PreparedStatement ps = conn .prepareStatement (eq .sql ,
73+ ResultSet .TYPE_SCROLL_INSENSITIVE , ResultSet .CONCUR_READ_ONLY )) {
74+ ps .setString (1 , month );
75+ ps .setString (2 , date );
76+ ps .setInt (3 , eq .durationThreshold );
77+ ps .setInt (4 , eq .countThreshold );
78+ try (ResultSet rs = ps .executeQuery ()) {
79+ while (rs .next ()) {
80+ double weight = rs .getInt (3 ) * (double ) rs .getFloat (4 );
81+ sb .append ('\n' ).append (eq .label ).append (' ' )
82+ .append (rs .getString (1 )).append (' ' )
83+ .append (rs .getString (2 )).append (' ' )
84+ .append (weight );
85+ }
86+ }
87+ }
88+ }
89+
90+ /**
91+ * Connects to the database and writes out the edge-list from the meeting
92+ * table, forming edges of kind: friends, classmates, study-groups,
93+ * strangers and familiar strangers (depending upon parameters).
2194 *
2295 * <p>The output path is validated to prevent directory traversal —
2396 * it must resolve to a location within the current working directory.</p>
2497 *
25- * @param path output file path (must be within the working directory)
26- * @param Month
27- * @param Date
28- * @param dThresF
29- * @param CThresF
30- * @param dThresFS
31- * @param CThresFS
32- * @param dThresC
33- * @param CThresC
34- * @param dThresS
35- * @param CThresS
36- * @param dThresSg
37- * @param CThresSg
38- * @throws Exception
98+ * @param path output file path (must be within the working directory)
99+ * @param month month filter
100+ * @param date date filter
101+ * @param dThresF duration threshold for friends
102+ * @param cThresF count threshold for friends
103+ * @param dThresFS duration threshold for familiar strangers
104+ * @param cThresFS count threshold for familiar strangers
105+ * @param dThresC duration threshold for classmates
106+ * @param cThresC count threshold for classmates
107+ * @param dThresS duration threshold for strangers
108+ * @param cThresS count threshold for strangers
109+ * @param dThresSg duration threshold for study groups
110+ * @param cThresSg count threshold for study groups
111+ * @throws Exception if database or I/O operations fail
39112 */
40- public static void generateFile (String path , String Month , String Date , int dThresF , int CThresF , int dThresFS , int CThresFS , int dThresC , int CThresC , int dThresS , int CThresS , int dThresSg , int CThresSg ) throws Exception {
113+ public static void generateFile (String path , String month , String date ,
114+ int dThresF , int cThresF , int dThresFS , int cThresFS ,
115+ int dThresC , int cThresC , int dThresS , int cThresS ,
116+ int dThresSg , int cThresSg ) throws Exception {
41117
42118 // Validate output path — prevent directory traversal attacks
43119 File outputFile = new File (path ).getCanonicalFile ();
@@ -50,137 +126,42 @@ public static void generateFile(String path, String Month, String Date, int dThr
50126
51127 System .out .println ("connecting..." );
52128
53- // Parameterized query template for location-based meeting queries.
54- // Parameters: month, date, location, duration threshold, count threshold.
55-
56- // --- Friends query ---
57- String friendSql = " SELECT x.id , y.id , C , d "
58- + " FROM ( SELECT imei1 , imei2, count(*) as C,avg(duration) as d"
59- + " FROM ( SELECT imei1, imei2, duration"
60- + " FROM meeting"
61- + " WHERE month = ? AND date = ? AND location= 'public' AND duration > ?) as b"
62- + " GROUP BY imei1, imei2) as a, deviceID as x, deviceID as y"
63- + " WHERE C >= ? AND a.imei1= x.imei AND a.imei2 = y.imei" ;
64-
65- // --- Study groups query ---
66- String studygSql = " SELECT x.id , y.id , C , d "
67- + " FROM ( SELECT imei1, imei2, count(*) as C,avg(duration) as d"
68- + " FROM ( SELECT imei1, imei2, duration"
69- + " FROM meeting"
70- + " WHERE month = ? AND date = ? AND location= 'class' AND duration > ?) as b"
71- + " GROUP BY imei1, imei2) as a, deviceID as x, deviceID as y"
72- + " WHERE C <= ? AND a.imei1= x.imei AND a.imei2 = y.imei" ;
73-
74- // --- Classmates query ---
75- String cmateSql = " SELECT x.id , y.id, C , d "
76- + " FROM ( SELECT imei1, imei2, count(*) as C, avg(duration) as d"
77- + " FROM ( SELECT imei1, imei2, duration"
78- + " FROM meeting"
79- + " WHERE month = ? AND date = ? AND location= 'class' AND duration > ?) as b"
80- + " GROUP BY imei1, imei2) as a, deviceID as x, deviceID as y"
81- + " WHERE C >= ? AND a.imei1= x.imei AND a.imei2 = y.imei" ;
82-
83- // --- Strangers query ---
84- // Exclude both 'class' and 'unknown' locations so only meetings with
85- // a resolved location (e.g. 'public', 'path') are considered.
86- String strangerSql = " SELECT x.id , y.id , C , d "
87- + " FROM ( SELECT imei1, imei2, count(*) as C,avg(duration) as d"
88- + " FROM ( SELECT imei1, imei2, duration"
89- + " FROM meeting"
90- + " WHERE month = ? AND date = ? AND location NOT IN ('class', 'unknown', '') AND duration < ?) as b"
91- + " GROUP BY imei1, imei2) as a, deviceID as x, deviceID as y"
92- + " WHERE C < ? AND a.imei1= x.imei AND a.imei2 = y.imei" ;
93-
94- // --- Familiar strangers query ---
95- String famstrangerSql = " SELECT x.id , y.id , C , d "
96- + " FROM ( SELECT imei1, imei2, count(*) as C, avg(duration) as d"
97- + " FROM ( SELECT imei1, imei2, duration"
98- + " FROM meeting"
99- + " WHERE month = ? AND date = ? AND location NOT IN ('class', 'unknown', '') AND duration < ?) as b"
100- + " GROUP BY imei1, imei2) as a , deviceID as x, deviceID as y"
101- + " WHERE C > ? AND a.imei1= x.imei AND a.imei2 = y.imei" ;
129+ // Define all edge queries — each differs only in location filter,
130+ // duration/count comparison operators, and threshold values.
131+ EdgeQuery [] queries = {
132+ new EdgeQuery (locationMatchSql (">" , ">=" ), "f" , dThresF , cThresF ), // friends (public, long duration, high count)
133+ new EdgeQuery (locationMatchSql (">" , "<=" ), "sg" , dThresSg , cThresSg ), // study groups (class, long duration, low count)
134+ new EdgeQuery (locationMatchSql (">" , ">=" ), "c" , dThresC , cThresC ), // classmates (class, long duration, high count)
135+ new EdgeQuery (locationExcludeSql ("<" ), "s" , dThresS , cThresS ), // strangers (non-class, short duration, low count)
136+ new EdgeQuery (locationExcludeSql (">" ), "fs" , dThresFS , cThresFS ), // familiar strangers (non-class, short duration, high count)
137+ };
102138
103- try (Connection conn = Util .getAppConnection ()) {
139+ // The friends query uses location='public', study groups and classmates use location='class'.
140+ // We need to bind the location parameter for LOCATION_MATCH_TEMPLATE queries.
141+ // Rework: use a 5-param version that includes location for match queries.
104142
105- // Use StringBuilder instead of String concatenation for performance
143+ try ( Connection conn = Util . getAppConnection ()) {
106144 StringBuilder sb = new StringBuilder ("edges" );
107145
108- // --- Friends ---
109- try (PreparedStatement psFriend = conn .prepareStatement (friendSql , ResultSet .TYPE_SCROLL_INSENSITIVE , ResultSet .CONCUR_READ_ONLY )) {
110- psFriend .setString (1 , Month );
111- psFriend .setString (2 , Date );
112- psFriend .setInt (3 , dThresF );
113- psFriend .setInt (4 , CThresF );
114- try (ResultSet rs = psFriend .executeQuery ()) {
115- while (rs .next ()) {
116- double weight = rs .getInt (3 ) * (double ) rs .getFloat (4 );
117- sb .append ("\n f " ).append (rs .getString (1 )).append (" " )
118- .append (rs .getString (2 )).append (" " ).append (weight );
119- }
120- }
121- }
122-
123- // --- Study groups ---
124- try (PreparedStatement psStudyg = conn .prepareStatement (studygSql , ResultSet .TYPE_SCROLL_INSENSITIVE , ResultSet .CONCUR_READ_ONLY )) {
125- psStudyg .setString (1 , Month );
126- psStudyg .setString (2 , Date );
127- psStudyg .setInt (3 , dThresSg );
128- psStudyg .setInt (4 , CThresSg );
129- try (ResultSet rs = psStudyg .executeQuery ()) {
130- while (rs .next ()) {
131- double weight = rs .getInt (3 ) * (double ) rs .getFloat (4 );
132- sb .append ("\n sg " ).append (rs .getString (1 )).append (" " )
133- .append (rs .getString (2 )).append (" " ).append (weight );
134- }
135- }
136- }
137-
138- // --- Classmates ---
139- try (PreparedStatement psCmate = conn .prepareStatement (cmateSql , ResultSet .TYPE_SCROLL_INSENSITIVE , ResultSet .CONCUR_READ_ONLY )) {
140- psCmate .setString (1 , Month );
141- psCmate .setString (2 , Date );
142- psCmate .setInt (3 , dThresC );
143- psCmate .setInt (4 , CThresC );
144- try (ResultSet rs = psCmate .executeQuery ()) {
145- while (rs .next ()) {
146- double weight = rs .getInt (3 ) * (double ) rs .getFloat (4 );
147- sb .append ("\n c " ).append (rs .getString (1 )).append (" " )
148- .append (rs .getString (2 )).append (" " ).append (weight );
149- }
150- }
151- }
152-
153- // --- Strangers ---
154- try (PreparedStatement psStranger = conn .prepareStatement (strangerSql , ResultSet .TYPE_SCROLL_INSENSITIVE , ResultSet .CONCUR_READ_ONLY )) {
155- psStranger .setString (1 , Month );
156- psStranger .setString (2 , Date );
157- psStranger .setInt (3 , dThresS );
158- psStranger .setInt (4 , CThresS );
159- try (ResultSet rs = psStranger .executeQuery ()) {
160- while (rs .next ()) {
161- double weight = rs .getInt (3 ) * (double ) rs .getFloat (4 );
162- sb .append ("\n s " ).append (rs .getString (1 )).append (" " )
163- .append (rs .getString (2 )).append (" " ).append (weight );
164- }
165- }
166- }
167-
168- // --- Familiar strangers ---
169- try (PreparedStatement psFamstranger = conn .prepareStatement (famstrangerSql , ResultSet .TYPE_SCROLL_INSENSITIVE , ResultSet .CONCUR_READ_ONLY )) {
170- psFamstranger .setString (1 , Month );
171- psFamstranger .setString (2 , Date );
172- psFamstranger .setInt (3 , dThresFS );
173- psFamstranger .setInt (4 , CThresFS );
174- try (ResultSet rs = psFamstranger .executeQuery ()) {
175- while (rs .next ()) {
176- double weight = rs .getInt (3 ) * (double ) rs .getFloat (4 );
177- sb .append ("\n fs " ).append (rs .getString (1 )).append (" " )
178- .append (rs .getString (2 )).append (" " ).append (weight );
179- }
180- }
181- }
182-
183- // Write output file — use validated outputFile, not raw path
146+ // Friends: location = 'public'
147+ executeLocationMatchQuery (conn , "f" , "public" , ">" , ">=" ,
148+ month , date , dThresF , cThresF , sb );
149+ // Study groups: location = 'class'
150+ executeLocationMatchQuery (conn , "sg" , "class" , ">" , "<=" ,
151+ month , date , dThresSg , cThresSg , sb );
152+ // Classmates: location = 'class'
153+ executeLocationMatchQuery (conn , "c" , "class" , ">" , ">=" ,
154+ month , date , dThresC , cThresC , sb );
155+ // Strangers: location NOT IN (...)
156+ executeEdgeQuery (conn ,
157+ new EdgeQuery (locationExcludeSql ("<" ), "s" , dThresS , cThresS ),
158+ month , date , sb );
159+ // Familiar strangers: location NOT IN (...)
160+ executeEdgeQuery (conn ,
161+ new EdgeQuery (locationExcludeSql (">" ), "fs" , dThresFS , cThresFS ),
162+ month , date , sb );
163+
164+ // Write output file
184165 if (outputFile .exists ()) {
185166 outputFile .delete ();
186167 }
@@ -189,4 +170,31 @@ public static void generateFile(String path, String Month, String Date, int dThr
189170 }
190171 }
191172 }
173+
174+ /**
175+ * Executes a location-match edge query (location = ?) with 5 bind parameters.
176+ */
177+ private static void executeLocationMatchQuery (Connection conn , String label ,
178+ String location , String durationOp , String countOp ,
179+ String month , String date , int durationThreshold , int countThreshold ,
180+ StringBuilder sb ) throws Exception {
181+ String sql = String .format (LOCATION_MATCH_TEMPLATE , durationOp , countOp );
182+ try (PreparedStatement ps = conn .prepareStatement (sql ,
183+ ResultSet .TYPE_SCROLL_INSENSITIVE , ResultSet .CONCUR_READ_ONLY )) {
184+ ps .setString (1 , month );
185+ ps .setString (2 , date );
186+ ps .setString (3 , location );
187+ ps .setInt (4 , durationThreshold );
188+ ps .setInt (5 , countThreshold );
189+ try (ResultSet rs = ps .executeQuery ()) {
190+ while (rs .next ()) {
191+ double weight = rs .getInt (3 ) * (double ) rs .getFloat (4 );
192+ sb .append ('\n' ).append (label ).append (' ' )
193+ .append (rs .getString (1 )).append (' ' )
194+ .append (rs .getString (2 )).append (' ' )
195+ .append (weight );
196+ }
197+ }
198+ }
199+ }
192200}
0 commit comments