1414import java .nio .file .Files ;
1515import java .nio .file .Path ;
1616import java .util .HashSet ;
17+ import java .util .List ;
1718import java .util .Set ;
1819
1920public class ExportPass implements Pass {
@@ -114,7 +115,7 @@ private void exportMethodsCsv(Path dir, GraphContext context) throws IOException
114115 try (FileWriter out = new FileWriter (dir .resolve ("methods.csv" ).toFile ());
115116 CSVPrinter printer = new CSVPrinter (out ,
116117 CSVFormat .Builder .create ().setHeader ("id" , "fqn" , "name" , "signature" , "isExternal" ,
117- "annotations" , "sourceCode" , "containingClassFqn" , " isLambda" , "filePath" ).build ())) {
118+ "annotations" , "sourceCode" , "isLambda" , "filePath" ).build ())) {
118119 for (MethodNode node : context .methods .values ()) {
119120 if (node .getId () != null && !node .getId ().isBlank ()) {
120121 String annotations = String .join (";" , node .getAnnotations ());
@@ -228,27 +229,37 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
228229 executeOrThrow (conn , "CREATE REL TABLE Calls(FROM Method TO Method)" );
229230
230231 Path tempDir = Files .createTempDirectory ("ladybug_import" );
232+
233+ // Take consistent snapshots to prevent race conditions from background threads
234+ // that may still be adding to the ConcurrentHashMap.
235+ List <ClassNode > classSnapshot = new java .util .ArrayList <>(context .classes .values ());
236+ List <MethodNode > methodSnapshot = new java .util .ArrayList <>(context .methods .values ());
237+ Set <String > classIds = new HashSet <>();
238+ Set <String > methodIds = new HashSet <>();
239+
231240 Path classesCsv = tempDir .resolve ("classes.csv" );
232241 try (FileWriter out = new FileWriter (classesCsv .toFile ());
233242 CSVPrinter printer = new CSVPrinter (out , CSVFormat .DEFAULT )) {
234- for (ClassNode node : context . classes . values () ) {
243+ for (ClassNode node : classSnapshot ) {
235244 if (node .getId () != null && !node .getId ().isBlank ()) {
236245 String annotations = String .join (";" , node .getAnnotations ());
237246 printer .printRecord (node .getId (), node .getFqn (), node .getName (), node .isInterface (),
238247 node .isExternal (), annotations , node .getDeclarationCode (), node .getFilePath ());
248+ classIds .add (node .getId ());
239249 }
240250 }
241251 }
242252
243253 Path methodsCsv = tempDir .resolve ("methods.csv" );
244254 try (FileWriter out = new FileWriter (methodsCsv .toFile ());
245255 CSVPrinter printer = new CSVPrinter (out , CSVFormat .DEFAULT )) {
246- for (MethodNode node : context . methods . values () ) {
256+ for (MethodNode node : methodSnapshot ) {
247257 if (node .getId () != null && !node .getId ().isBlank ()) {
248258 String annotations = String .join (";" , node .getAnnotations ());
249259 printer .printRecord (node .getId (), node .getFqn (), node .getName (), node .getSignature (),
250260 node .isExternal (), annotations , node .getSourceCode (), node .isLambda (),
251261 node .getFilePath ());
262+ methodIds .add (node .getId ());
252263 }
253264 }
254265 }
@@ -261,7 +272,9 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
261272 CSVPrinter implPrinter = new CSVPrinter (implOut , CSVFormat .DEFAULT )) {
262273 for (InheritanceEdge edge : context .inheritanceEdges ) {
263274 if (edge .getChildFqn () != null && !edge .getChildFqn ().isBlank () &&
264- edge .getParentFqn () != null && !edge .getParentFqn ().isBlank ()) {
275+ edge .getParentFqn () != null && !edge .getParentFqn ().isBlank () &&
276+ classIds .contains (edge .getChildFqn ()) &&
277+ classIds .contains (edge .getParentFqn ())) {
265278 if ("EXTENDS" .equals (edge .getType ())) {
266279 extPrinter .printRecord (edge .getChildFqn (), edge .getParentFqn ());
267280 } else {
@@ -276,7 +289,9 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
276289 CSVPrinter printer = new CSVPrinter (out , CSVFormat .DEFAULT )) {
277290 for (MethodCallEdge edge : context .callEdges ) {
278291 if (edge .getCallerMethodFqn () != null && !edge .getCallerMethodFqn ().isBlank () &&
279- edge .getCalledMethodFqn () != null && !edge .getCalledMethodFqn ().isBlank ()) {
292+ edge .getCalledMethodFqn () != null && !edge .getCalledMethodFqn ().isBlank () &&
293+ methodIds .contains (edge .getCallerMethodFqn ()) &&
294+ methodIds .contains (edge .getCalledMethodFqn ())) {
280295 printer .printRecord (edge .getCallerMethodFqn (), edge .getCalledMethodFqn ());
281296 }
282297 }
@@ -285,10 +300,12 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
285300 Path definesCsv = tempDir .resolve ("defines.csv" );
286301 try (FileWriter out = new FileWriter (definesCsv .toFile ());
287302 CSVPrinter printer = new CSVPrinter (out , CSVFormat .DEFAULT )) {
288- for (MethodNode node : context . methods . values () ) {
303+ for (MethodNode node : methodSnapshot ) {
289304 if (node .getContainingClassFqn () != null && !node .getContainingClassFqn ().isBlank () &&
290- node .getFqn () != null && !node .getFqn ().isBlank ()) {
291- printer .printRecord (node .getContainingClassFqn (), node .getFqn ());
305+ node .getId () != null && !node .getId ().isBlank () &&
306+ classIds .contains (node .getContainingClassFqn ()) &&
307+ methodIds .contains (node .getId ())) {
308+ printer .printRecord (node .getContainingClassFqn (), node .getId ());
292309 }
293310 }
294311 }
@@ -446,10 +463,11 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
446463 }
447464 }
448465 for (MethodNode node : context .methods .values ()) {
449- if (node .getContainingClassFqn () != null ) {
466+ if (node .getContainingClassFqn () != null && node .getId () != null &&
467+ !node .getId ().isBlank ()) {
450468 addBatch .accept (String .format (
451469 "MATCH (a:Class {id: '%s'}), (b:Method {id: '%s'}) MERGE (a)-[r:Defines]->(b)" ,
452- escape (node .getContainingClassFqn ()), escape (node .getFqn ())));
470+ escape (node .getContainingClassFqn ()), escape (node .getId ())));
453471 edgeCount ++;
454472 }
455473 }
0 commit comments