Movie Database Case: An EMF-I NC Q UERY Solution∗ Gábor Szárnyas Oszkár Semeráth Benedek Izsó Csaba Debreceni Ábel Hegedüs Zoltán Ujhelyi Gábor Bergmann Budapest University of Technology and Economics, Department of Measurement and Information Systems, H-1117 Magyar tudósok krt. 2., Budapest, Hungary {szarnyas, semerath, izso, debreceni, abel.hegedus, ujhelyiz, bergmann}@mit.bme.hu This paper presents a solution for the Movie Database Case of the Transformation Tool Contest 2014, using EMF-I NC Q UERY and Xtend for implementing the model transformation. 1 Introduction Automated model transformations are frequently integrated to modeling environments, requiring both high performance and a concise programming interface to support software engineers. The objective of the EMF-I NC Q UERY [2] framework is to provide a declarative way to define queries over EMF models. EMF-I NC Q UERY extended the pattern language of VIATRA with new features (including transitive closure, role navigation, match count) and tailored it to EMF models [1]. EMF-I NC Q UERY is developed with a focus on incremental query evaluation. The latest develop- ments extend this concept by providing a preliminary rule execution engine to perform transformations. As the engine is under heavy development, the design of a dedicated rule language (instead of using the API of the engine) is currently subject to future work. Conceptually, the environment relies on graph transformation (GT) rules: conditions are specified as EMF-I NC Q UERY patterns, while model manipu- lation and environment configuration is managed using the Xtend language [3]. One case study of the 2014 Transformation Tool Contest describes a movie database transforma- tion [4]. The main characteristics of the transformation related to the application of EMF-I NC Q UERY are that i) it only adds new elements to the input model (i.e. couple and group are does not modify the input model), and ii) it is non-incremental (i.e. creating a new group will not affect rule applicability). The rest of the paper is structured as follows: Section 2 gives an overview of the implementation, Section 3 describes the solution including measurement results, and Section 4 concludes our paper. 2 Architecture Overview The overview of the rule-based solution is illustrated in Figure 1a. The input of the transformation is a movie model. The result is a transformed movie model including various groups (couples and n-cliques) and their average rating [4]. The transformation runs in a Java application, that uses pattern matchers provided by EMF-I NC Q UERY and model manipulation specified in Xtend. The pattern matcher monitors ∗ This work was partially supported by the MONDO (EU ICT-611125) and TÁMOP (4.2.2.B-10/1–2010-0009) projects. This research was realized in the frames of TÁMOP 4.2.4. A/1-11-1-2012-0001 „National Excellence Program – Elaborating and operating an inland student and researcher personal support system”. The project was subsidized by the European Union and co-financed by the European Social Fund. © G. Szárnyas et al. Submitted to: This work is licensed under the TTC 2014 Creative Commons Attribution License. 2 Movie Database Case: An EMF-I NC Q UERY solution ContainedElement children 0..* Root <> Movie 0..* MovieType titleG:GEString commonMovies MOVIE ratingG:GEDouble TV yearG:GEInt Group VIDEO typeG:GMovieType avgRatingG:GEDouble VIDEOGAME EAttribute0 Java application movies 0..* Rule Rule persons 0..* condition consequence p1 0..1 Person Couple nameG:GEString Pattern « notifies » Rule execution p2 0..1 Clique matcher engine 0..* Movie model Transformed persons movie model EMF « monitors » « modifies » resources Actor Actress (a) The specification and runtime (b) Our extended metamodel Figure 1: Overview of our approach the resources to incrementally update match sets. The application initially reads the input movie database, creates the output resources, then executes the transformation, and finally serializes the results into files. In the Ecore model of the specification, no containment hierarchy is used and all objects are held in the contents list of the EMF resource. However, the performance of the transformation was affected by the resource implementation used (since it will determine the implementation of the list operations). To avoid this issue, we have extended the metamodel by a Root object (see Figure 1b). This object serves as a container for all Group, Movie and Person objects. According to our experiments, this increases the speed of the pattern matching by a factor of two. For compatibility with the specification, we ensured that our solution works with the models provided and persists outputs in a format without this root element. 3 Solution 3.1 Patterns and Transformations Task 1: Generating Test Data The synthetic test data is generated in Xtend (see Listing A.2.1). The code tightly follows the specification defined in the case description [4]. Task 2: Finding Couples Couples are listed with the following pattern: 1 pattern pe r so ns T oC ou p le ( p1name , p2name ) { 2 find cast ( p1name , M1 ) ; find cast ( p2name , M1 ) ; 3 find cast ( p1name , M2 ) ; find cast ( p2name , M2 ) ; 4 find cast ( p1name , M3 ) ; find cast ( p2name , M3 ) ; 5 M1 != M2 ; M2 != M3 ; M1 != M3 ; 6 check ( p1name < p2name ) ; 7 } 8 pattern cast ( name , M ) { Movie . persons . name (M , name ) ; } 9 pattern personName (p , pName ) { Person . name (p , pName ) ; } Note that the cast pattern returns the names of persons that play in a given movie. This is important since the names of the persons can be used to avoid symmetric matches in the personsToCouple pattern by sorting. The Couple objects are created and configured in Xtend (see createCouples in line 45 of Listing A.2.2). This includes setting the p1 and p2 references using a personName pattern and computing the commonMovies by simple set intersection operators (retainAll). G. Szárnyas et al. 3 Task 3: Computing Average Rankings The average rankings are computed in Xtend by calculating the mean of the rating attributes of a couple’s common movies (see calculateAvgRatings in line 122 of Listing A.2.2). The movies are enumerated with the following pattern: 1 pattern c o m m o n M o v i e s O f C o u p l e (c , m ) { Couple . commonMovies (c , m ) ; } Extension Task 1: Compute Top-15 Couples This task is mostly implemented in Xtend (see top- GroupByRating in line 70 and topGroupByCommonMovies in line 84 of Listing A.2.2), however, it uses the groupSize pattern in order to filter the groups with the particular number of members. 1 pattern groupSize ( group , S ) { 2 Group ( group ) ; 3 S == count find memberOfGroup (_ , group ) ; 4 } This pattern uses the count find construct which computes the number of matches for a given pattern. Additionally, specific comparators are used to sort and determine the top-15 lists by rating or number of common movies (see Listing A.2.4). Extension Task 2: Finding Cliques The pattern for finding cliques is implemented similarly to the personsToCouple pattern 3.1. The pattern for 3-cliques is defined as follows: 1 pattern p e r s o n s T o 3 C l i q u e ( P1 , P2 , P3 ) { 2 find cast ( P1 , M1 ) ; find cast ( P2 , M1 ) ; find cast ( P3 , M1 ) ; 3 find cast ( P1 , M2 ) ; find cast ( P2 , M2 ) ; find cast ( P3 , M2 ) ; 4 find cast ( P1 , M3 ) ; find cast ( P2 , M3 ) ; find cast ( P3 , M3 ) ; 5 M1 != M2 ; M2 != M3 ; M1 != M3 ; 6 check ( P1 < P2 ) ; check ( P2 < P3 ) ; 7 check ( P1 < P3 ) ; 8 } The creation of cliques is done similarly to couples (see createCliques in line 138 of Listing A.2.2). However, this pattern has a redundant check constraint, as P1 < P2 and P2 < P3 already imply P1 < P3 . This works as a hint for the query engine and allows it to filter the permutation of the results (e.g. (a2 , a1 , a3 ), (a1 , a3 , a2 ), . . .)) earlier. For performance considerations, additional patterns were defined manually for 4- and 5-cliques. For larger cliques (n > 5), patterns could be automatically generated using code generation techniques. General solution for n-cliques. We also provide the outline for a more general solution (for arbitrary n values). For the sake of clarity, we will refer to couples as 2-cliques. In this approach, the cliques are built iteratively. Suppose we already have all k-cliques in the graph (e.g. we already added the 2-, 3-, 4- and 5-cliques with the previous patterns). To get the (k + 1)-cliques, we look for a group g0 and a person p0 that (i) have at least 3 movies in common, (ii) g = g0 ∪ {p0 } is a group that is not a subset of any other groups (see Figure 2). Formally, (ii) can be expressed as (6 ∃g0 ) : g ⊆ g0 . Using g = g0 ∪ {p0 }, we derive the following expression (6 ∃g0 ) : (g0 ⊆ g0 )∧(p0 ∈ g0 ). The g0 ⊆ g0 expression can be formulated as follows: (∀p ∈ g0 ) : p ∈ g0 . As the EMF-I NC Q UERY Pattern Language does not have a universal quantifier, we rewrite this using the existential quantifier: (6 ∃p ∈ g0 ) : p 6∈ g0 . The resulting expression for condition (ii) is the following: (6 ∃g0 ) : ((6 ∃p ∈ g0 ) : p 6∈ g0 ) ∧ (p0 ∈ g0 ). We have implemented this general solution (see Listing A.2.3). Pattern subsetOfGroup implements condition (ii), while nextClique pattern is capable of determining the (k + 1)-cliques given a model 4 Movie Database Case: An EMF-I NC Q UERY solution m m m m m g0 p0 g Figure 2: Matching 3-clique groups in the positive test pattern. g0 is a couple. containing all k-cliques. This approach is functionally correct, however, it only works for very small input models and hence is omitted from our measurements. Extension Task 3: The average rankings are computed the same way as in task 3. Extension Task 4: The top 15 average rankings are computed the same way as in extension task 2. 3.2 Optimizations To increase the performance of the transformations, we carried out some optimizations. (1) The com- mon movies of the two Person objects are computed from Xtend instead of EMF-I NC Q UERY. (2) The patterns for 3-, 4- and 5-cliques are implemented manually. (3) Common subpatterns were identified and extracted them into separate patterns, as the engine can reuse the pattern for each occurrence, and makes the query definition file easier to maintain. For an example, see the cast pattern in A.1. 3.3 Benchmark Results The implementation was benchmarked in the SHARE cloud, on an Ubuntu 12.04 64-bit operating system running in a VirtualBox environment. The virtual machine used one core of an Intel Xeon E5-2650 CPU and had 6 GB of RAM. The transformations were ran in a timeout window of 10 minutes. 3.4 Synthetic model Results are displayed in Figure 3. The diagram shows the transformation times for creating couples and cliques for synthetic models. The results show that the transformations run in near linear time. The dominating factor of the running time is the initialization of the query engine. However, after initialization, creating groups can be carried out efficiently. Furthermore, our experiments showed that the limiting factor for our solution is the memory consumption of the incremental query engine. Given more memory, the solution is capable of transforming larger models as well. 3.5 IMDb model In the given time range and memory constraints, the transformation of the IMDb model could only generate the couples and 3-cliques for the smallest instance model. Finding the couples took 3 minutes, G. Szárnyas et al. 5 Runtime of Transformation Tasks 100 90 80 70 60 Time (s) 50 40 30 20 10 0 0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 Size (N) Couples 3-Clique 4-Clique 5-Clique Figure 3: Benchmark results while finding 3-cliques took 6. However, in case of a live and evolving model, our solution is capable of incrementally running the transformation which in practice results in near instantaneous response time. 3.6 Transformation Correctness and Reproducibility Our solution was developed as Eclipse plug-ins, however, it is also available as a command line applica- tion compiled using the Apache Maven.The transformation runs correctly for the provided test cases on SHARE1 , and the source code is also available in a Git repository2 . The results of the transformations were spot-checked for both synthetic and IMDb models. 4 Conclusion In this paper we have presented our implementation of the Movie Database Case. The solution uses EMF-I NC Q UERY as a model query engine: the transformation is specified using declarative graph pat- tern queries over EMF models for rule preconditions, and Xtend code for model manipulations. The main conclusion of the performance evaluation is that EMF-IncQuery’s incremental approach is not a good fit for this case study as the transformation is very model manipulation dominant. References [1] Gábor Bergmann, Zoltán Ujhelyi, István Ráth & Dániel Varró (2011): A Graph Query Language for EMF models. In: Theory and Practice of Model Transformations, Fourth Int. Conf., LNCS 6707, Springer. [2] Eclipse.org (2014): EMF-IncQuery. http://eclipse.org/incquery/. [3] Eclipse.org (2014): Xtend – Modernized Java. https://www.eclipse.org/xtend/. [4] Matthias Tichy Tassilo Horn, Christian Krause (2014): The TTC 2014 Movie Database Case. In: 7th Trans- formation Tool Contest (TTC 2014), EPTCS. 1 http://is.ieis.tue.nl/staff/pvgorp/share/?page=ConfigureNewSession&vdi=Ubuntu12LTS_TTC14_ 64bit_TTC14-EIQ-imdb.vdi 2 https://git.inf.mit.bme.hu/w?p=projects/viatra/ttc14-eiq.git (username: anonymous, no password). 6 Movie Database Case: An EMF-I NC Q UERY solution A Appendix – Movie Database Case Transformation Code A.1 EMF-I NC Q UERY Graph Patterns 1 package hu . bme . mit . ttc . imdb . queries 2 3 import " http :// movies /1.0 " 4 5 6 // Shorthand patterns 7 pattern personName (p , pName ) { 8 Person . name (p , pName ) ; 9 } 10 11 // Actor with name is part of the case of movie M 12 pattern cast ( name , M ) { 13 Movie . persons . name (M , name ) ; 14 } 15 16 // Movie m is a common movie of Couple c 17 pattern c o m m o n M o v i e s O f C o u p l e (c , m ) { 18 Couple . commonMovies (c , m ) ; 19 } 20 21 /* * 22 * This pattern determines if a person is a member of a group . 23 */ 24 pattern memberOfGroup ( person , group ) { 25 Couple . p1 ( group , person ) ; 26 } or { 27 Couple . p2 ( group , person ) ; 28 } or { 29 Clique . persons ( group , person ) ; 30 } 31 32 /* * 33 * This pattern determines the size of a group . 34 */ 35 pattern groupSize ( group , S ) { 36 Group ( group ) ; 37 S == count find memberOfGroup (_ , group ) ; 38 } 39 40 // Couple patterns 41 /* * 42 * This pattern looks for two person names ( p1name , p2name ) , who were in the cast of 43 * three different movies ( M1 , M2 , M3 ) . 44 * The names are ordered l e x i c o g r a p h i c a l l y in order to list the same pair only one 45 * ( the match set contains only {( a1 , a2 ) } instead of {( a1 , a2 ) , ( a2 , a1 ) }. 46 */ 47 pattern pe r so ns T oC ou p le ( p1name , p2name ) { 48 find cast ( p1name , M1 ) ; find cast ( p2name , M1 ) ; 49 find cast ( p1name , M2 ) ; find cast ( p2name , M2 ) ; 50 find cast ( p1name , M3 ) ; find cast ( p2name , M3 ) ; 51 52 M1 != M2 ; M2 != M3 ; M1 != M3 ; 53 54 check ( p1name < p2name ) ; 55 } 56 57 /* * 58 * This pattern looks for the common movies of a couple . 59 * The couple is determined with the pe rs o ns T oC ou p le pattern . 60 */ G. Szárnyas et al. 7 61 pattern c o m m o n M o v i e s T o C o u p l e ( p1name , p2name , m ) { 62 find pe r so ns T oC ou p le ( p1name , p2name ) ; 63 64 Person . movies ( p1 , m ) ; 65 Person . movies ( p2 , m ) ; 66 Person . name ( p1 , p1name ) ; 67 Person . name ( p2 , p2name ) ; 68 69 check ( p1name < p2name ) ; 70 } 71 72 /* * 73 * Returns with the number of common movies of a couple . 74 */ 75 pattern c o u n t O f C o m m o n M o v i e s O f C o u p l e ( p1 , p2 , n ) { 76 Couple . p1 (c , p1 ) ; 77 Couple . p2 (c , p2 ) ; 78 n == count find c o m m o n M o v i e s O f C o u p l e (c , _m ) ; 79 } 80 81 // Clique patterns 82 /* * 83 * Similarly to the couple pattern , this pattern looks for 3 - cliques . 84 */ 85 pattern p e r s o n s T o 3 C l i q u e ( P1 , P2 , P3 ) { 86 find cast ( P1 , M1 ) ; find cast ( P2 , M1 ) ; find cast ( P3 , M1 ) ; 87 find cast ( P1 , M2 ) ; find cast ( P2 , M2 ) ; find cast ( P3 , M2 ) ; 88 find cast ( P1 , M3 ) ; find cast ( P2 , M3 ) ; find cast ( P3 , M3 ) ; 89 90 M1 != M2 ; M2 != M3 ; M1 != M3 ; 91 92 check ( P1 < P2 ) ; check ( P2 < P3 ) ; 93 } 94 95 /* * 96 * Similarly to the couple pattern , this pattern looks for 4 - cliques . 97 */ 98 pattern p e r s o n s T o 4 C l i q u e ( P1 , P2 , P3 , P4 ) { 99 find cast ( P1 , M1 ) ; find cast ( P2 , M1 ) ; find cast ( P3 , M1 ) ; find cast ( P4 , M1 ) ; 100 find cast ( P1 , M2 ) ; find cast ( P2 , M2 ) ; find cast ( P3 , M2 ) ; find cast ( P4 , M2 ) ; 101 find cast ( P1 , M3 ) ; find cast ( P2 , M3 ) ; find cast ( P3 , M3 ) ; find cast ( P4 , M3 ) ; 102 103 M1 != M2 ; M2 != M3 ; M1 != M3 ; 104 105 check ( P1 < P2 ) ; check ( P2 < P3 ) ; check ( P3 < P4 ) ; 106 } 107 108 /* * 109 * Similarly to the couple pattern , this pattern looks for 5 - cliques . 110 */ 111 pattern p e r s o n s T o 5 C l i q u e ( P1 , P2 , P3 , P4 , P5 ) { 112 find cast ( P1 , M1 ) ; find cast ( P2 , M1 ) ; find cast ( P3 , M1 ) ; find cast ( P4 , M1 ) ; find cast ( P5 , M1 ) ; 113 find cast ( P1 , M2 ) ; find cast ( P2 , M2 ) ; find cast ( P3 , M2 ) ; find cast ( P4 , M2 ) ; find cast ( P5 , M2 ) ; 114 find cast ( P1 , M3 ) ; find cast ( P2 , M3 ) ; find cast ( P3 , M3 ) ; find cast ( P4 , M3 ) ; find cast ( P5 , M3 ) ; 115 116 M1 != M2 ; M2 != M3 ; M1 != M3 ; 117 118 check ( P1 < P2 ) ; check ( P2 < P3 ) ; check ( P3 < P4 ) ; check ( P4 < P5 ) ; 119 } 8 Movie Database Case: An EMF-I NC Q UERY solution A.2 Xtend Code A.2.1 Generator Code 1 /* * 2 * This class implements the test model generator logic . 3 */ 4 class Generator { 5 6 // The EMF resource on which the tr ansfor mation operates 7 public Resource r 8 9 // We define this extension to help with model element creation 10 extension MoviesFactory = MoviesFactory . eINSTANCE 11 12 // method to generate an example of size N 13 def generate ( int N ) { 14 createExample ( N ) ; 15 } 16 17 // create N test cases in the model 18 def createExample ( int N ) { 19 (0 .. N - 1) . forEach [ createTest ( it ) ] 20 } 21 22 // create a test cases in the model with parameter n 23 def createTest ( int n ) { 24 crea tePosi tive ( n ) 25 crea teNega tive ( n ) 26 } 27 28 // create a positive match for the test case 29 // initialize some movies and actors / actresses 30 // create i n t e r c o n n e c t i o n s according to a logic that will yield a positive match 31 def create Positi ve ( int n ) { 32 val movies = newArrayList () 33 (0 .. 4) . forEach [ movies += createMovie (10 * n + it ) ] 34 35 val a = createActor ( " a " + (10 * n)) 36 val b = createActor ( " a " + (10 * n + 1) ) 37 val c = createActor ( " a " + (10 * n + 2) ) 38 val d = createActress ( " a " + (10 * n + 3) ) 39 val e = createActress ( " a " + (10 * n + 4) ) 40 41 val actors = #[ a , b , c , d , e ] 42 val firstTwo = #[ a , b ] 43 val lastTwo = #[ d, e] 44 45 movies . get (0) . persons += firstTwo ; 46 (1 .. 3) . forEach [ movies . get ( it ) . persons += actors ] 47 movies . get (4) . persons += lastTwo 48 49 r . contents += actors 50 r . contents += movies 51 } 52 53 // create a positive match for the test case 54 // initialize some movies and actors / actresses 55 // create i n t e r c o n n e c t i o n s according to a logic that will yield a negative match 56 def create Negati ve ( int n ) { 57 val movies = newArrayList () 58 (5 .. 9) . forEach [ movies += createMovie (10 * n + it ) ] 59 60 val a = createActor ( " a " + (10 * n + 5) ) 61 val b = createActor ( " a " + (10 * n + 6) ) G. Szárnyas et al. 9 62 val c = createActress ( " a " + (10 * n + 7) ) 63 val d = createActress ( " a " + (10 * n + 8) ) 64 val e = createActress ( " a " + (10 * n + 9) ) 65 66 val actors = #[ a , b, c, d, e] 67 movies . get (0) . persons += #[ a , b] 68 movies . get (1) . persons += #[ a , b, c] 69 movies . get (2) . persons += #[ b, c, d] 70 movies . get (3) . persons += #[ c, d, e] 71 movies . get (4) . persons += #[ d, e] 72 73 r . contents += actors 74 r . contents += movies 75 } 76 77 // create a movie with the given rating 78 def createMovie ( int rating ) { 79 val movie = createMovie 80 movie . rating = rating 81 movie 82 } 83 84 // create an actor with the given name 85 def createActor ( String name ) { 86 val actor = createActor 87 actor . name = name 88 actor 89 } 90 91 // create an actress with the given name 92 def createActress ( String name ) { 93 val actress = createActress 94 actress . name = name 95 actress 96 } 97 98 } A.2.2 Transformation Code 1 /* * 2 * This class implements the t ransfo rmatio n logic . 3 */ 4 class Tr ansfor mation { 5 6 /* * 7 * Initialize the transf ormati on processor on a resource . 8 * The runtime of the trans format ion steps are logged . 9 * @param r The target resource of the tr ansfor mation . 10 * @param bmr The benchmark logger . 11 */ 12 new ( Resource r , B e n c h m a r k R e s u l t s bmr ) { 13 this . r = r ; 14 this . bmr = bmr ; 15 this . root = r . contents . get (0) as Root 16 } 17 18 // to store the benchmark results 19 protected val B e n c h m a r k R e s u l t s bmr ; 20 // to store the model 21 protected Resource r 22 23 // //// Resources Management 24 protected val Root root ; 25 /* * 10 Movie Database Case: An EMF-I NC Q UERY solution 26 * Helper function to add elements to the target resource . 27 * @param 28 */ 29 def a d d E l e m e n t T o R e s o u r c e ( C o n t a i n e d E l e m e n t c o n t a i n e d E l e m e n t ) { 30 root . children . add ( c o n t a i n e d E l e m e n t ) 31 } 32 def a d d E l e m e n t s T o R e s o u r c e ( Collection c o n t a i n e d E l e m e n t s ) { 33 root . children . addAll ( c o n t a i n e d E l e m e n t s ) 34 } 35 def g e t E l e m e n t s F r o m R e s o u r c e () { 36 root . children 37 } 38 // / / / / / / / / / / / / / / / / / / / / / / / / / / 39 40 // to help with model manipulation 41 extension MoviesFactory = MoviesFactory . eINSTANCE 42 extension Imdb = Imdb . instance 43 44 // create couples 45 public def createCouples () { 46 val engine = A d v a n c e d I n c Q u e r y E n g i n e . c r e a t e U n m a n a g e d E n g i n e ( r ) 47 val coupleMatcher = engine . p er so n sT oC o up l e 48 val c o m m o n M o v i e s M a t c h e r = engine . c o m m o n M o v i e s T o C o u p l e 49 val p e r s o n N a m e M a t c h e r = engine . personName 50 51 val newCouples = new LinkedList < Couple > 52 coupleMatcher . forEachMatch [ 53 val couple = createCouple () 54 val p1 = p e r s o n N a m e M a t c h e r . g et Al l Va lu e sO fp ( p1name ) . head 55 val p2 = p e r s o n N a m e M a t c h e r . g et Al l Va lu e sO fp ( p2name ) . head 56 couple . setP1 ( p1 ) 57 couple . setP2 ( p2 ) 58 val commonMovies = c o m m o n M o v i e s M a t c h e r . g et Al l Va lu e sO fm ( p1name , p2name ) 59 couple . commonMovies . addAll ( commonMovies ) 60 61 newCouples += couple 62 ] 63 64 println ( " # of couples = " + newCouples . size ) 65 engine . dispose 66 a d d E l e m e n t s T o R e s o u r c e ( newCouples ) ; 67 } 68 69 // calculate the top group by rating 70 def t o p G r o u p B y R a t i n g ( int size ) { 71 println ( " Top -15 by Average Rating " ) 72 println ( " = = = = = = = = = = = = = = = = = = = = = = = = " ) 73 val n = 15; 74 75 val engine = Inc QueryE ngine . on ( r ) 76 val c o u p l e W i t h R a t i n g M a t c h e r = engine . groupSize 77 val rankedCouples = c o u p l e W i t h R a t i n g M a t c h e r . g e t A l l V a l u e s O f g r o u p ( size ) . sort ( 78 new G r o u p A V G C o m p a r a t o r ) 79 80 printCouples (n , rankedCouples ) 81 } 82 83 // calculate the top group by common movies 84 def t o p G r o u p B y C o m m o n M o v i e s ( int size ) { 85 println ( " Top -15 by Number of Common Movies " ) 86 println ( " = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = " ) 87 88 val n = 15; 89 val engine = Inc QueryE ngine . on ( r ) 90 val c o u p l e W i t h R a t i n g M a t c h e r = engine . groupSize G. Szárnyas et al. 11 91 92 val rankedCouples = c o u p l e W i t h R a t i n g M a t c h e r . g e t A l l V a l u e s O f g r o u p ( size ) . sort ( 93 new G r o u p S i z e C o m p a r a t o r 94 ) 95 printCouples (n , rankedCouples ) 96 } 97 98 // pretty - print couples 99 def printCouples ( int n , List < Group > rankedCouples ) { 100 (0 .. n - 1) . forEach [ 101 if ( it < rankedCouples . size ) { 102 val c = rankedCouples . get ( it ) ; 103 println ( c . printGroup ( it ) ) 104 } 105 ] 106 } 107 108 // pretty - print groups 109 def printGroup ( Group group , int lineNumber ) { 110 if ( group instanceof Couple ) { 111 val couple = group as Couple 112 return ’’’« lineNumber » . Couple avgRating « group . avgRating » , « group . commonMovies . size » movies ( « couple . p1 . name » ; « couple . p2 . name » ) ’’’ 113 } 114 else { 115 val clique = group as Clique 116 return ’’’« lineNumber » . « clique . persons . size » - Clique avgRating « group . avgRating » , « group . commonMovies . size » movies ( « 117 FOR person : clique . persons SEPARATOR " , " » « person . name » « ENDFOR » ) ’’’ 118 } 119 } 120 121 // calculate average ratings 122 def c a l c u l a t e A v g R a t i n g s () { 123 g e t E l e m e n t s F r o m R e s o u r c e . filter ( typeof ( Group ) ) . forEach [ x | c a l c u l a t e A v g R a t i n g ( x . commonMovies , x ) ] 124 } 125 126 // calculate average rating 127 protected def c a l c u l a t e A v g R a t i n g ( Collection < Movie > commonMovies , Group group ) { 128 var sumRating = 0.0 129 130 for ( m : commonMovies ) { 131 sumRating = sumRating + m . rating 132 } 133 val n = commonMovies . size 134 group . avgRating = sumRating / n 135 } 136 137 // create cliques 138 public def createCliques ( int cliques ) { 139 val engine = A d v a n c e d I n c Q u e r y E n g i n e . c r e a t e U n m a n a g e d E n g i n e ( r ) 140 val personMatcher = getPersonName ( engine ) 141 var Collection < Clique > newCliques 142 143 if ( cliques == 3) { 144 val clique3 = g e t P e r s o n s T o 3 C l i q u e ( engine ) 145 146 newCliques = clique3 . allMatches . map [ x | ge nerate Clique ( 147 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p1 ) .p , 148 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p2 ) .p , 149 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p3 ) . p ) ]. toList ; 150 } 151 else if ( cliques == 4) { 152 val clique4 = g e t P e r s o n s T o 4 C l i q u e ( engine ) 12 Movie Database Case: An EMF-I NC Q UERY solution 153 154 newCliques = clique4 . allMatches . map [ x | ge nerate Clique ( 155 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p1 ) .p , 156 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p2 ) .p , 157 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p3 ) .p , 158 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p4 ) . p ) ]. toList ; 159 } 160 else if ( cliques == 5) { 161 val clique5 = g e t P e r s o n s T o 5 C l i q u e ( engine ) 162 newCliques = clique5 . allMatches . map [ x | ge nerate Clique ( 163 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p1 ) .p , 164 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p2 ) .p , 165 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p3 ) .p , 166 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p4 ) .p , 167 personMatcher . g e t O n e A r b i t r a r y M a t c h ( null , x . p5 ) . p ) ]. toList ; 168 } 169 170 println ( " # of " + cliques + " - cliques = " + newCliques . size ) 171 172 engine . dispose 173 newCliques . forEach [ x | x . commonMovies . addAll ( x . c o l l e c t C o m m o n M o v i e s ) ] 174 a d d E l e m e n t s T o R e s o u r c e ( newCliques ) ; 175 } 176 177 // generate cliques 178 protected def ge nerate Clique ( Person ... persons ) { 179 val c = createClique 180 c . persons += persons 181 return c 182 } 183 184 // collect common movies 185 protected def c o l l e c t C o m m o n M o v i e s ( Clique clique ) { 186 var Set < Movie > commonMovies = null ; 187 for ( personMovies : clique . persons . map [ movies ]) { 188 if ( commonMovies == null ) { 189 commonMovies = personMovies . toSet ; 190 } 191 else { 192 commonMovies . retainAll ( personMovies ) 193 } 194 } 195 return commonMovies 196 } 197 } A.2.3 General Clique Patterns The resulting expression for condition (ii) is the following: (6 ∃g0 ) : ((6 ∃p0 ∈ g0 ) : p0 6∈ g0 ) ∧ (p ∈ g0 ). This is equivalent to the following EMF-I NC Q UERY pattern: 1 /* * Group g0 is a subset of Group gx . */ 2 pattern subsetOfGroup ( g0 : Group , gx : Group ) { 3 neg find n o t S u b s e t O f G r o u p ( p0 , g0 , gx ) ; 4 } 5 6 /* * This pattern returns is a helper for the subsetOfGroup pattern . */ 7 pattern n o t S u b s e t O f G r o u p ( p0 : Person , g0 : Group , gx : Group ) { 8 find memberOfGroup ( p0 , g0 ) ; 9 neg find memberOfGroup ( p0 , gx ) ; 10 } 11 12 /* * Person p is a member of Group g . A Group is either a Couple or a Clique . */ 13 pattern memberOfGroup (p , g ) { 14 Couple . p1 (g , p ) ; G. Szárnyas et al. 13 15 } or { 16 Couple . p2 (g , p ) ; 17 } or { 18 Clique . persons (g , p ) ; 19 } Based on the subsetOfGroup pattern, we may implement the nextClique pattern like follows: 1 /* * the nextCliques pattern */ 2 pattern nextCliques ( g : Group , p : Person ) { 3 neg find a l p h a b e t i c a l l y L a t e r M e m b e r O f G r o u p (g , p ) ; 4 n == count find c o m m o n M o v i e O f G r o u p A n d P e r s o n (g , p , m ) ; 5 check ( n >= 3) ; 6 neg find union (g , p ) ; 7 } 8 9 /* * p is a member of g for which another a lphabe ticall y previous member exists */ 10 pattern a l p h a b e t i c a l l y L a t e r M e m b e r O f G r o u p ( g : Group , p : Person ) { 11 find memberOfGroup (m , g ) ; 12 Person . name (p , pName ) ; 13 Person . name (m , mName ) ; 14 check ( mName >= pName ) ; 15 } 16 17 /* * m is a common movie of g and p */ 18 pattern c o m m o n M o v i e O f G r o u p A n d P e r s o n (g , p , m ) { 19 find c o m m o n M o v i e s O f G r o u p (g , m ) ; 20 Person . movies (p , m ) ; 21 } 22 23 /* * m is a common movie of g */ 24 pattern c o m m o n M o v i e s O f G r o u p (g , m ) { 25 Group . commonMovies (g , m ) ; 26 } 27 28 /* * p is in g0 */ 29 pattern union ( g0 , p ) { 30 find memberOfGroup (p , gx ) ; 31 find subsetOfGroup ( g0 , gx ) ; 32 } A.2.4 Comparator Code for Top-15 1 class G r o u p S i z e C o m p a r a t o r implements Comparator < Group >{ 2 3 override compare ( Group arg0 , Group arg1 ) { 4 if ( arg0 . commonMovies . size < arg1 . commonMovies . size ) { return 1} 5 else if ( arg0 . commonMovies . size == arg1 . commonMovies . size ) { return 0} 6 else return -1; 7 } 8 } 9 10 class G r o u p A V G C o m p a r a t o r implements Comparator < Group >{ 11 12 override compare ( Group arg0 , Group arg1 ) { 13 if ( arg0 . avgRating < arg1 . avgRating ) { return 1;} 14 else if ( arg0 . avgRating == arg1 . avgRating ) { return 0;} 15 else return -1; 16 } 17 }