So I have data for a sort of friends system stored on a MySQL database, and a sort of API to retrieve the data in Java objects.
The Java object (MPlayer) contains things like the username, online status, friends (ids separated by ":"). The MPlayer object takes a player's unique ID as a constructor.
The data is saved in the object when created and when the reload() method is called. This is instead of accessing the database every time I want to get something like the username. The reason for this is that I need to get the data in a loop to be displayed on a GUI, and I obviously don't want to download the data every frame. Instead, I just use the reload method every 6 seconds or so.
One of the functions is getFriends() and returns a list of MPlayer. The list is stored when the MPlayer object is created. The problem is, when each of the MPlayer friends are created, it creates a list for their friends, which in turn creates a list for their friends, ending up with a StackOverFlowException due to recursion.
What would be a good solution to avoid the error?
Code in question:
The MPlayer constructor/load method:
public MPlayer(String player){
this.uuid = player;
try {
st.setString(1, uuid);
} catch (SQLException e) {
e.printStackTrace();
}
this.reload();
}
public void reload(){
try {
ResultSet set = st.executeQuery();
if(set.next()){
if(set.getString("server").equals("none")){
isConnected = false;
}else{
isConnected = true;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
ResultSet set = st.executeQuery();
if(set.next()){
this.serverIP = set.getString("server");
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
ResultSet set = st.executeQuery();
if(set.next()){
this.username = set.getString("username");
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
ResultSet get = st.executeQuery();
if(get.next()){
this.online = get.getBoolean("status");
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
List<MPlayer> list = new ArrayList<MPlayer>();
ResultSet get = st.executeQuery();
if(get.next()){
for(String str : get.getString("friends").split(":")){
if(!str.equalsIgnoreCase("none")){
MPlayer player = new MPlayer(str);
if(player.isOnline()){
list.add(0,player);
}else{
list.add(player);
}
}
}
}
this.friends = list;
} catch (SQLException e) {
e.printStackTrace();
}
this.settings = new Settings(this);
PreparedStatement state = Main.getPreparedStatement("SELECT * FROM updates WHERE uuid=?");
try {
state.setString(1, this.getUUID());
ResultSet set2 = state.executeQuery();
List<StatusUpdate> updates = new ArrayList<StatusUpdate>();
while(set2.next()){
updates.add(new StatusUpdate(set2.getInt(1)));
}
Collections.sort(updates, new Comparator<StatusUpdate>() {
@Override
public int compare(StatusUpdate r1, StatusUpdate r2) {
return -1 * r1.getDate().compareTo(r2.getDate());
}
});
this.updates = updates;
} catch (SQLException e) {
e.printStackTrace();
}
List<StatusUpdate> updates = new ArrayList<StatusUpdate>();
for(MPlayer p : this.getFriends()){
updates.addAll(p.getStatusUpdates());
}
updates.addAll(this.getStatusUpdates());
Collections.sort(updates, new Comparator<StatusUpdate>() {
public int compare(StatusUpdate m1, StatusUpdate m2) {
return -1 * m1.getDate().compareTo(m2.getDate());
}
});
this.timeline = updates;
}
The obvious answer is "quit doing it recursively with no termination".
You don't want unbounded closure on the friends list -- but what do you want? Just the list of friends? In that case, perhaps you need a separate object type, such as "FriendList". In creating this, you merely list the friend IDs; don't load a friend record until it's specifically accessed.
Another way is to code the number of levels you want to activate, making this a parameter of the object load. For isntance, load your primary MPlayer with a depth of N=2. For each friend of your primary, load to a depth of N-1. When you hit 0, list the ID without loading the record (as above).
Does this get you moving toward a solution?