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

Commit a84e342

Browse files
refactor: extract MeetingQueryConfig to eliminate 13-param method and 5 duplicate SQL strings
Network.generateFile() had 13 parameters and 5 near-identical SQL query strings that only differed in location filter and comparison operators. Changes: - New MeetingQueryConfig class encapsulates per-relationship-type query differences (location filter mode, comparison direction, thresholds) - MeetingQueryConfig.buildSql() generates the parameterised SQL from config - New generateFile(path, month, date, List<MeetingQueryConfig>) overload - Original 13-param method preserved as @deprecated for backward compat - Zero behavior change: deprecated method delegates to new one
1 parent fc509df commit a84e342

2 files changed

Lines changed: 190 additions & 79 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package app;
2+
3+
/**
4+
* Configuration for a single meeting-relationship query.
5+
*
6+
* <p>Each relationship type (friends, classmates, study-groups, strangers,
7+
* familiar strangers) uses the same parameterised SQL pattern but differs
8+
* in location filter, duration comparison direction, and count comparison
9+
* direction. This class captures those differences so that
10+
* {@link Network} can iterate a list of configs instead of repeating
11+
* five near-identical SQL strings.</p>
12+
*
13+
* @author zalenix
14+
*/
15+
public final class MeetingQueryConfig {
16+
17+
/** Location filter mode — determines the WHERE clause for the location column. */
18+
public enum LocationFilter {
19+
/** Match a single exact location value (e.g. 'public', 'class'). */
20+
EXACT,
21+
/** Exclude a set of location values (NOT IN). */
22+
EXCLUDE
23+
}
24+
25+
/** Comparison direction for the threshold in the outer query. */
26+
public enum Comparison {
27+
/** Use {@code >=} for the count threshold. */
28+
GTE,
29+
/** Use {@code <=} for the count threshold. */
30+
LTE,
31+
/** Use {@code <} for the count threshold. */
32+
LT,
33+
/** Use {@code >} for the count threshold. */
34+
GT
35+
}
36+
37+
private final String edgePrefix;
38+
private final LocationFilter locationFilter;
39+
private final String[] locationValues;
40+
private final Comparison durationComparison;
41+
private final Comparison countComparison;
42+
private final int durationThreshold;
43+
private final int countThreshold;
44+
45+
/**
46+
* @param edgePrefix edge type prefix written to output (e.g. "f", "sg")
47+
* @param locationFilter how to filter on the location column
48+
* @param locationValues location value(s) for the filter
49+
* @param durationComparison comparison operator for duration in the inner query
50+
* @param countComparison comparison operator for count in the outer query
51+
* @param durationThreshold duration threshold value
52+
* @param countThreshold count threshold value
53+
*/
54+
public MeetingQueryConfig(String edgePrefix,
55+
LocationFilter locationFilter,
56+
String[] locationValues,
57+
Comparison durationComparison,
58+
Comparison countComparison,
59+
int durationThreshold,
60+
int countThreshold) {
61+
this.edgePrefix = edgePrefix;
62+
this.locationFilter = locationFilter;
63+
this.locationValues = locationValues;
64+
this.durationComparison = durationComparison;
65+
this.countComparison = countComparison;
66+
this.durationThreshold = durationThreshold;
67+
this.countThreshold = countThreshold;
68+
}
69+
70+
public String getEdgePrefix() { return edgePrefix; }
71+
public int getDurationThreshold() { return durationThreshold; }
72+
public int getCountThreshold() { return countThreshold; }
73+
74+
/**
75+
* Builds the parameterised SQL query string for this configuration.
76+
*
77+
* <p>The query always expects 4 positional parameters:
78+
* {@code ?1=month, ?2=date, ?3=durationThreshold, ?4=countThreshold}.</p>
79+
*
80+
* @return the SQL query string
81+
*/
82+
public String buildSql() {
83+
String durationOp = comparisonToSql(durationComparison);
84+
String countOp = comparisonToSql(countComparison);
85+
String locationClause = buildLocationClause();
86+
87+
return " SELECT x.id , y.id , C , d "
88+
+ " FROM ( SELECT imei1, imei2, count(*) as C, avg(duration) as d"
89+
+ " FROM ( SELECT imei1, imei2, duration"
90+
+ " FROM meeting"
91+
+ " WHERE month = ? AND date = ? AND " + locationClause
92+
+ " AND duration " + durationOp + " ?) as b"
93+
+ " GROUP BY imei1, imei2) as a, deviceID as x, deviceID as y"
94+
+ " WHERE C " + countOp + " ? AND a.imei1= x.imei AND a.imei2 = y.imei";
95+
}
96+
97+
private String buildLocationClause() {
98+
if (locationFilter == LocationFilter.EXACT) {
99+
return "location= '" + locationValues[0] + "'";
100+
} else {
101+
// NOT IN ('class', 'unknown', '')
102+
StringBuilder sb = new StringBuilder("location NOT IN (");
103+
for (int i = 0; i < locationValues.length; i++) {
104+
if (i > 0) sb.append(", ");
105+
sb.append("'").append(locationValues[i]).append("'");
106+
}
107+
sb.append(")");
108+
return sb.toString();
109+
}
110+
}
111+
112+
private static String comparisonToSql(Comparison c) {
113+
switch (c) {
114+
case GTE: return ">=";
115+
case LTE: return "<=";
116+
case LT: return "<";
117+
case GT: return ">";
118+
default: return ">=";
119+
}
120+
}
121+
}

Gvisual/src/app/Network.java

Lines changed: 69 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,43 @@
66
import java.sql.Connection;
77
import java.sql.PreparedStatement;
88
import java.sql.ResultSet;
9+
import java.util.Arrays;
10+
import java.util.List;
11+
12+
import app.MeetingQueryConfig.Comparison;
13+
import app.MeetingQueryConfig.LocationFilter;
914

1015
/**
16+
* Generates Edge-list files from the meeting database.
17+
*
18+
* <p>Connects to the PostgreSQL meeting database and produces an Edge-list
19+
* file for each relationship type (friends, classmates, study-groups,
20+
* strangers, familiar strangers) based on configurable duration and
21+
* frequency thresholds.</p>
1122
*
23+
* <p>The output path is validated to prevent directory traversal —
24+
* it must resolve to a location within the current working directory.</p>
1225
*
1326
* @author zalenix
1427
*/
1528
public class Network {
1629

1730
/**
31+
* Generates the edge-list file from the meeting database.
1832
*
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-
*
22-
* <p>The output path is validated to prevent directory traversal —
23-
* it must resolve to a location within the current working directory.</p>
33+
* <p>Replaces the previous 13-parameter signature with a structured
34+
* parameter object approach. Each relationship type is described by
35+
* a {@link MeetingQueryConfig} that encapsulates the SQL pattern
36+
* differences (location filter, comparison direction, thresholds).</p>
2437
*
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
38+
* @param path output file path (must be within the working directory)
39+
* @param month month filter (e.g. "03")
40+
* @param date date filter (e.g. "15")
41+
* @param configs list of relationship query configurations
42+
* @throws Exception if database access or file I/O fails
3943
*/
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 {
41-
44+
public static void generateFile(String path, String month, String date,
45+
List<MeetingQueryConfig> configs) throws Exception {
4246
// Validate output path — prevent directory traversal attacks
4347
File outputFile = new File(path).getCanonicalFile();
4448
File workingDir = new File(".").getCanonicalFile();
@@ -50,67 +54,14 @@ public static void generateFile(String path, String Month, String Date, int dThr
5054

5155
System.out.println("connecting...");
5256

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";
102-
10357
try (Connection conn = Util.getAppConnection()) {
104-
105-
// Use StringBuilder instead of String concatenation for performance
10658
StringBuilder sb = new StringBuilder("edges");
10759

108-
// Execute each relationship query using the shared helper
109-
appendEdges(conn, sb, friendSql, "f", Month, Date, dThresF, CThresF);
110-
appendEdges(conn, sb, studygSql, "sg", Month, Date, dThresSg, CThresSg);
111-
appendEdges(conn, sb, cmateSql, "c", Month, Date, dThresC, CThresC);
112-
appendEdges(conn, sb, strangerSql, "s", Month, Date, dThresS, CThresS);
113-
appendEdges(conn, sb, famstrangerSql, "fs", Month, Date, dThresFS, CThresFS);
60+
for (MeetingQueryConfig config : configs) {
61+
appendEdges(conn, sb, config.buildSql(), config.getEdgePrefix(),
62+
month, date, config.getDurationThreshold(),
63+
config.getCountThreshold());
64+
}
11465

11566
// Write output file — use validated outputFile, not raw path
11667
if (outputFile.exists()) {
@@ -122,6 +73,45 @@ public static void generateFile(String path, String Month, String Date, int dThr
12273
}
12374
}
12475

76+
/**
77+
* Backward-compatible overload preserving the original 13-parameter signature.
78+
*
79+
* <p>Delegates to {@link #generateFile(String, String, String, List)} by
80+
* constructing {@link MeetingQueryConfig} instances from the raw threshold
81+
* parameters.</p>
82+
*
83+
* @deprecated Use {@link #generateFile(String, String, String, List)} with
84+
* explicit {@link MeetingQueryConfig} objects instead.
85+
*/
86+
@Deprecated
87+
public static void generateFile(String path, String Month, String Date,
88+
int dThresF, int CThresF, int dThresFS, int CThresFS,
89+
int dThresC, int CThresC, int dThresS, int CThresS,
90+
int dThresSg, int CThresSg) throws Exception {
91+
92+
String[] excludedLocations = {"class", "unknown", ""};
93+
94+
List<MeetingQueryConfig> configs = Arrays.asList(
95+
new MeetingQueryConfig("f", LocationFilter.EXACT,
96+
new String[]{"public"}, Comparison.GT, Comparison.GTE,
97+
dThresF, CThresF),
98+
new MeetingQueryConfig("sg", LocationFilter.EXACT,
99+
new String[]{"class"}, Comparison.GT, Comparison.LTE,
100+
dThresSg, CThresSg),
101+
new MeetingQueryConfig("c", LocationFilter.EXACT,
102+
new String[]{"class"}, Comparison.GT, Comparison.GTE,
103+
dThresC, CThresC),
104+
new MeetingQueryConfig("s", LocationFilter.EXCLUDE,
105+
excludedLocations, Comparison.LT, Comparison.LT,
106+
dThresS, CThresS),
107+
new MeetingQueryConfig("fs", LocationFilter.EXCLUDE,
108+
excludedLocations, Comparison.LT, Comparison.GT,
109+
dThresFS, CThresFS)
110+
);
111+
112+
generateFile(path, Month, Date, configs);
113+
}
114+
125115
/**
126116
* Executes a parameterized meeting query and appends edges to the output buffer.
127117
*

0 commit comments

Comments
 (0)