33
33
import java .nio .file .Paths ;
34
34
import java .util .*;
35
35
import java .util .concurrent .CopyOnWriteArrayList ;
36
+ import java .util .concurrent .ThreadPoolExecutor ;
36
37
import java .util .concurrent .atomic .AtomicReference ;
37
38
import java .util .function .BiFunction ;
38
39
import java .util .function .Consumer ;
94
95
import org .exist .xquery .update .Modification ;
95
96
import org .exist .xquery .util .SerializerUtils ;
96
97
import org .exist .xquery .value .*;
98
+ import org .jgrapht .alg .interfaces .ShortestPathAlgorithm ;
99
+ import org .jgrapht .alg .shortestpath .TransitNodeRoutingShortestPath ;
100
+ import org .jgrapht .graph .DefaultEdge ;
101
+ import org .jgrapht .graph .DefaultGraphType ;
102
+ import org .jgrapht .opt .graph .fastutil .FastutilMapGraph ;
103
+ import org .jgrapht .util .ConcurrencyUtil ;
104
+ import org .jgrapht .util .SupplierUtil ;
97
105
import org .w3c .dom .Node ;
98
106
99
107
import static com .evolvedbinary .j8fu .OptionalUtil .or ;
@@ -224,6 +232,11 @@ public class XQueryContext implements BinaryValueManager, Context {
224
232
*/
225
233
private Object2ObjectMap <String , Module []> allModules = new Object2ObjectOpenHashMap <>();
226
234
235
+ /**
236
+ * Describes a graph of all the modules and how they import each other.
237
+ */
238
+ private @ Nullable final FastutilMapGraph <ModuleVertex , DefaultEdge > modulesDependencyGraph ;
239
+
227
240
/**
228
241
* Used to save current state when modules are imported dynamically
229
242
*/
@@ -454,6 +467,10 @@ public XQueryContext(@Nullable final Database db, @Nullable final Configuration
454
467
}
455
468
456
469
protected XQueryContext (@ Nullable final Database db , @ Nullable final Configuration configuration , @ Nullable final Profiler profiler , final boolean loadDefaults ) {
470
+ this (db , configuration , profiler , loadDefaults , new FastutilMapGraph <>(null , SupplierUtil .createDefaultEdgeSupplier (), DefaultGraphType .directedPseudograph ().asUnweighted ()));
471
+ }
472
+
473
+ protected XQueryContext (@ Nullable final Database db , @ Nullable final Configuration configuration , @ Nullable final Profiler profiler , final boolean loadDefaults , final @ Nullable FastutilMapGraph <ModuleVertex , DefaultEdge > modulesDependencyGraph ) {
457
474
this .db = db ;
458
475
459
476
// if needed, fallback to db.getConfiguration
@@ -474,6 +491,8 @@ protected XQueryContext(@Nullable final Database db, @Nullable final Configurati
474
491
this .profiler = new Profiler (null );
475
492
}
476
493
494
+ this .modulesDependencyGraph = modulesDependencyGraph ;
495
+
477
496
this .watchdog = new XQueryWatchDog (this );
478
497
479
498
// load configuration defaults
@@ -1529,6 +1548,65 @@ public void addModule(final String namespaceURI, final Module module) {
1529
1548
addRootModule (namespaceURI , module );
1530
1549
}
1531
1550
1551
+ /**
1552
+ * Add a vertex to the Modules Dependency Graph.
1553
+ *
1554
+ * @param moduleVertex the module vertex
1555
+ */
1556
+ protected void addModuleVertex (final ModuleVertex moduleVertex ) {
1557
+ modulesDependencyGraph .addVertex (moduleVertex );
1558
+ }
1559
+
1560
+ /**
1561
+ * Check if a vertex exists in the Modules Dependency Graph.
1562
+ *
1563
+ * @param moduleVertex the module vertex to look for
1564
+ *
1565
+ * @return true if the module vertex exists, false otherwise
1566
+ */
1567
+ protected boolean hasModuleVertex (final ModuleVertex moduleVertex ) {
1568
+ return modulesDependencyGraph .containsVertex (moduleVertex );
1569
+ }
1570
+
1571
+ /**
1572
+ * Add an edge between two Modules in the Dependency Graph.
1573
+ *
1574
+ * @param source the importing module
1575
+ * @param sink the imported module
1576
+ */
1577
+ protected void addModuleEdge (final ModuleVertex source , final ModuleVertex sink ) {
1578
+ modulesDependencyGraph .addEdge (source , sink );
1579
+ }
1580
+
1581
+ /**
1582
+ * Look for a path between two Modules in the Dependency Graph.
1583
+ *
1584
+ * @param source the module to start searching from
1585
+ * @param sink the destination module to attempt to reach
1586
+ *
1587
+ * @return true, if there is a path between the mdoules, false otherwise
1588
+ */
1589
+ protected boolean hasModulePath (final ModuleVertex source , final ModuleVertex sink ) {
1590
+ if (modulesDependencyGraph == null ) {
1591
+ return false ;
1592
+ }
1593
+
1594
+ ThreadPoolExecutor executor = null ;
1595
+ try {
1596
+ executor = ConcurrencyUtil .createThreadPoolExecutor (2 );
1597
+ final ShortestPathAlgorithm <ModuleVertex , DefaultEdge > spa = new TransitNodeRoutingShortestPath <>(modulesDependencyGraph , executor );
1598
+ return spa .getPath (source , sink ) != null ;
1599
+ } finally {
1600
+ if (executor != null ) {
1601
+ try {
1602
+ ConcurrencyUtil .shutdownExecutionService (executor );
1603
+ } catch (final InterruptedException e ) {
1604
+ Thread .currentThread ().interrupt ();
1605
+ }
1606
+ }
1607
+ }
1608
+ }
1609
+
1532
1610
protected void setRootModules (final String namespaceURI , @ Nullable final Module [] modules ) {
1533
1611
if (modules == null ) {
1534
1612
allModules .remove (namespaceURI ); // unbind the module
@@ -2641,6 +2719,11 @@ private ExternalModule compileOrBorrowModule(final String namespaceURI, final St
2641
2719
}
2642
2720
2643
2721
final ExternalModuleImpl modExternal = new ExternalModuleImpl (namespaceURI , prefix );
2722
+
2723
+ // NOTE(AR) this is needed to support cyclic imports in XQuery 3.1, see: https://github.com/eXist-db/exist/pull/4996
2724
+ addModule (namespaceURI , modExternal );
2725
+ addModuleVertex (new ModuleVertex (namespaceURI , location ));
2726
+
2644
2727
final XQueryContext modContext = new ModuleContext (this , namespaceURI , prefix , location );
2645
2728
modExternal .setContext (modContext );
2646
2729
final XQueryLexer lexer = new XQueryLexer (modContext , reader );
@@ -3547,4 +3630,52 @@ public String getStringValue() {
3547
3630
3548
3631
return sb .toString ();
3549
3632
}
3633
+
3634
+ @ Immutable
3635
+ public static class ModuleVertex {
3636
+ private final String namespaceURI ;
3637
+ private final String location ;
3638
+
3639
+ public ModuleVertex (final String namespaceURI ) {
3640
+ this .namespaceURI = namespaceURI ;
3641
+ this .location = null ;
3642
+ }
3643
+
3644
+ public ModuleVertex (final String namespaceURI , final String location ) {
3645
+ this .namespaceURI = namespaceURI ;
3646
+ this .location = location ;
3647
+ }
3648
+
3649
+ @ Override
3650
+ public boolean equals (final Object o ) {
3651
+ if (this == o ) {
3652
+ return true ;
3653
+ }
3654
+
3655
+ if (o == null || getClass () != o .getClass ()) {
3656
+ return false ;
3657
+ }
3658
+
3659
+ final ModuleVertex that = (ModuleVertex ) o ;
3660
+ if (!namespaceURI .equals (that .namespaceURI )) {
3661
+ return false ;
3662
+ }
3663
+ return location .equals (that .location );
3664
+ }
3665
+
3666
+ @ Override
3667
+ public int hashCode () {
3668
+ int result = namespaceURI .hashCode ();
3669
+ result = 31 * result + location .hashCode ();
3670
+ return result ;
3671
+ }
3672
+
3673
+ @ Override
3674
+ public String toString () {
3675
+ return "Module{" +
3676
+ "namespaceURI='" + namespaceURI + '\'' +
3677
+ "location='" + location + '\'' +
3678
+ '}' ;
3679
+ }
3680
+ }
3550
3681
}
0 commit comments