diff --git a/application.properties b/application.properties index 1dea0ec1..31409967 100644 --- a/application.properties +++ b/application.properties @@ -1,5 +1,5 @@ #Grails Metadata file -#Thu Feb 28 15:57:41 CET 2013 -app.grails.version=2.2.1 +#Wed May 15 15:47:34 CEST 2013 +app.grails.version=2.2.2 app.name=transmart-core -plugins.hibernate=2.2.1 +plugins.hibernate=2.2.2 diff --git a/grails-app/conf/BuildConfig.groovy b/grails-app/conf/BuildConfig.groovy index 7be9ec55..66f50283 100644 --- a/grails-app/conf/BuildConfig.groovy +++ b/grails-app/conf/BuildConfig.groovy @@ -2,7 +2,7 @@ grails.project.class.dir = "target/classes" grails.project.test.class.dir = "target/test-classes" grails.project.test.reports.dir = "target/test-reports" -grails.project.repos.default = 'repo.hyve.nl-snapshots' +grails.project.repos.default = 'repo.thehyve.nl-snapshots' grails.project.repos."${grails.project.repos.default}".url = 'http://repo.thehyve.nl/content/repositories/snapshots/' grails.project.dependency.resolution = { @@ -18,7 +18,7 @@ grails.project.dependency.resolution = { mavenLocal() mavenCentral() mavenRepo([ - name: 'repo.hyve.nl-snapshots', + name: 'repo.thehyve.nl-snapshots', root: 'http://repo.thehyve.nl/content/repositories/snapshots/', ]) } @@ -45,11 +45,13 @@ grails.project.dependency.resolution = { compile(':db-reverse-engineer:0.5') { exported: false } build(":tomcat:$grailsVersion", - ":release:2.2.0", + ":release:2.2.1", ":rest-client-builder:1.0.3", ) { exported: false } + + test ":code-coverage:1.2.6" } // see http://jira.grails.org/browse/GPRELEASE-42 diff --git a/grails-app/conf/Config.groovy b/grails-app/conf/Config.groovy index ee1e9cf9..5a74d018 100644 --- a/grails-app/conf/Config.groovy +++ b/grails-app/conf/Config.groovy @@ -1,7 +1,7 @@ // configuration for plugin testing - will not be included in the plugin zip def dataSourceConfig = new File("${userHome}/" + - ".grails/transmartConfig/DataSource.groovy") + ".grails/transmartConfig/DataSource-coredb.groovy") if (!dataSourceConfig.exists()) throw new RuntimeException("Coult not find ${dataSourceConfig}") diff --git a/grails-app/domain/org/transmartproject/db/highdim/DeChromosomalRegion.groovy b/grails-app/domain/org/transmartproject/db/highdim/DeChromosomalRegion.groovy index 968866f8..82038035 100644 --- a/grails-app/domain/org/transmartproject/db/highdim/DeChromosomalRegion.groovy +++ b/grails-app/domain/org/transmartproject/db/highdim/DeChromosomalRegion.groovy @@ -9,6 +9,7 @@ class DeChromosomalRegion implements Region { Long end Integer numberOfProbes String name + String cytoband /* unused */ String geneSymbol @@ -41,6 +42,7 @@ class DeChromosomalRegion implements Region { end nullable: true numberOfProbes nullable: true name nullable: true, maxSize: 100 + cytoband nullable: true, maxSize: 100 geneSymbol nullable: true, maxSize: 100 geneId nullable: true organism nullable: true, maxSize: 200 diff --git a/grails-app/services/org/transmartproject/db/dataquery/DataQueryResourceNoGormService.groovy b/grails-app/services/org/transmartproject/db/dataquery/DataQueryResourceNoGormService.groovy new file mode 100644 index 00000000..ff172e1f --- /dev/null +++ b/grails-app/services/org/transmartproject/db/dataquery/DataQueryResourceNoGormService.groovy @@ -0,0 +1,137 @@ +package org.transmartproject.db.dataquery + +import groovy.transform.CompileStatic +import org.hibernate.Query +import org.hibernate.ScrollableResults +import org.hibernate.Session +import org.hibernate.StatelessSession +import org.hibernate.impl.AbstractSessionImpl +import org.transmartproject.core.dataquery.DataQueryResource +import org.transmartproject.core.dataquery.acgh.RegionResult +import org.transmartproject.core.dataquery.assay.Assay +import org.transmartproject.core.dataquery.constraints.ACGHRegionQuery +import org.transmartproject.core.dataquery.acgh.Region +import org.transmartproject.core.dataquery.acgh.ACGHValues +import org.transmartproject.core.dataquery.acgh.CopyNumberState +import org.transmartproject.core.dataquery.Platform +import org.transmartproject.core.dataquery.acgh.RegionRow + +import static org.hibernate.ScrollMode.FORWARD_ONLY + +class DataQueryResourceNoGormService extends DataQueryResourceService { + + @Override + protected RegionResult getRegionResultForAssays(final List assays, AbstractSessionImpl session) { + def mainHQL = ''' + select + acgh.assay.id, + acgh.chipCopyNumberValue, + acgh.segmentCopyNumberValue, + acgh.flag, + acgh.probabilityOfLoss, + acgh.probabilityOfNormal, + acgh.probabilityOfGain, + acgh.probabilityOfAmplification, + + region.id, + region.cytoband, + region.chromosome, + region.start, + region.end, + region.numberOfProbes + from DeSubjectAcghData as acgh + inner join acgh.region region + where acgh.assay.id in (:assayIds) + order by region.id, acgh.assay.id'''.stripIndent() + + def mainQuery = createQuery(session, mainHQL, ['assayIds': assays.collect {Assay assay -> assay.id}]).scroll(FORWARD_ONLY) + + new RegionResultListImpl(assays, mainQuery) + } + + @CompileStatic + class ACGHValuesImpl implements ACGHValues { + final List rowList + + ACGHValuesImpl(final List rowList) { + this.rowList = rowList + } + + Long getAssayId() { rowList[0] as Long } + + Double getChipCopyNumberValue() { rowList[1] as Double } + + Double getSegmentCopyNumberValue() { rowList[2] as Double } + + CopyNumberState getCopyNumberState() { CopyNumberState.forInteger((rowList[3] as Short).intValue()) } + + Double getProbabilityOfLoss() { rowList[4] as Double } + + Double getProbabilityOfNormal() { rowList[5] as Double } + + Double getProbabilityOfGain() { rowList[6] as Double } + + Double getProbabilityOfAmplification() { rowList[7] as Double } + + } + + @CompileStatic + class RegionImpl implements Region { + + final List rowList + + RegionImpl(final List rowList) { + this.rowList = rowList + } + + Long getId() { rowList[8] as Long } + + String getCytoband() { rowList[9] as String } + + Platform getPlatform() { + throw new UnsupportedOperationException('Getter for get platform is not implemented') + } + + String getChromosome() { rowList[10] as String } + + Long getStart() { rowList[11] as Long } + + Long getEnd() { rowList[12] as Long } + + Integer getNumberOfProbes() { rowList[13] as Integer } + + } + + @CompileStatic + class RegionResultListImpl extends org.transmartproject.db.dataquery.RegionResultImpl { + RegionResultListImpl(List indicesList, ScrollableResults results) { + super(indicesList, results) + } + + @Override + protected RegionRow getNextRegionRow() { + List rowList = results.get() as List + if (rowList == null) { + return null + } + + RegionImpl region = new RegionImpl(rowList) + ACGHValuesImpl acghValue = new ACGHValuesImpl(rowList) + + Map values = new HashMap(indicesList.size(), 1) + while (new RegionImpl(rowList).id == region.id) { + values[acghValue.assayId] = acghValue + + if (!results.next()) { + break + } + rowList = results.get() as List + acghValue = new ACGHValuesImpl(rowList) + } + + new RegionRowImpl(region, indicesList, values) + } + } +} + + diff --git a/grails-app/services/org/transmartproject/db/dataquery/DataQueryResourceService.groovy b/grails-app/services/org/transmartproject/db/dataquery/DataQueryResourceService.groovy index 6454989e..fb689e71 100644 --- a/grails-app/services/org/transmartproject/db/dataquery/DataQueryResourceService.groovy +++ b/grails-app/services/org/transmartproject/db/dataquery/DataQueryResourceService.groovy @@ -1,14 +1,13 @@ package org.transmartproject.db.dataquery -import org.apache.commons.beanutils.PropertyUtils import org.hibernate.Query import org.hibernate.Session import org.hibernate.StatelessSession +import org.hibernate.impl.AbstractSessionImpl import org.transmartproject.core.dataquery.DataQueryResource import org.transmartproject.core.dataquery.acgh.RegionResult import org.transmartproject.core.dataquery.assay.Assay import org.transmartproject.core.dataquery.constraints.ACGHRegionQuery -import org.transmartproject.db.highdim.DeSubjectAcghData import static org.hibernate.ScrollMode.FORWARD_ONLY @@ -19,35 +18,42 @@ class DataQueryResourceService implements DataQueryResource { @Override RegionResult runACGHRegionQuery(ACGHRegionQuery spec, session) { if (!(session == null || session instanceof Session || session - instanceof StatelessSession)) + instanceof StatelessSession)) { throw new IllegalArgumentException('Expected session to be null ' + 'or an instance of type org.hibernate.Session or ' + 'org.hibernate.StatelessSession') + } validateQuery(spec) session = session ?: sessionFactory.currentSession + List assays = getAssaysForACGHRegionQuery(spec, session) + if (log.isDebugEnabled()) { + log.debug("Found ${assays.size()} assays: " + + assays.collect { + "{id: $it.id, subjectId: $it.subjectId}" + }.join(", ")) + } + + getRegionResultForAssays(assays, session) + } - def whereClauses, - params + protected List getAssaysForACGHRegionQuery(ACGHRegionQuery spec, AbstractSessionImpl session) { + def whereClauses, params /* first obtain list of assays */ whereClauses = [] params = [:] - populateWhereClauses(spec,whereClauses, params) + populateWhereClauses(spec, whereClauses, params) def assayHQL = 'from DeSubjectSampleMapping assay\n' assayHQL <<= 'where ' + whereClauses.join("\nand ") + "\n" assayHQL <<= 'order by assay\n' - List assays = createQuery(session, assayHQL, params).list() - if (log.isDebugEnabled()) { - log.debug("Found ${assays.size()} assays: " + - assays.collect { - "{id: $it.id, subjectId: $id.subjectId}" - }.join(", ")) - } + createQuery(session, assayHQL, params).list() + } + protected RegionResult getRegionResultForAssays(final List assays, AbstractSessionImpl session) { /* Then obtain the meat. * * Doing 'select acgh from ... inner join fetch acgh.region' should @@ -67,21 +73,23 @@ class DataQueryResourceService implements DataQueryResource { * query before just to get the regions. This would minimize the * amount of data that the postgres server has to send us. */ - def mainHQL = 'select acgh, acgh.region\n' + - 'from DeSubjectAcghData as acgh\n' + - 'inner join acgh.assay assay\n' - //'inner join acgh.region\n' - mainHQL <<= 'where ' + whereClauses.join("\nand ") + "\n" - mainHQL <<= 'order by acgh.region.id, assay\n' - - new RegionResultImpl(assays, - createQuery(session, mainHQL, params).scroll(FORWARD_ONLY)) + def mainHQL = ''' + select acgh, acgh.region + from DeSubjectAcghData as acgh + inner join acgh.assay assay + where assay in (:assays) + order by acgh.region.id, assay''' + + def mainQuery = createQuery(session, mainHQL, ['assays': assays]).scroll(FORWARD_ONLY) + + new RegionResultImpl(assays, mainQuery) } private void validateQuery(ACGHRegionQuery q) { def checkEmptiness = { it, name -> - if (!it) + if (!it) { throw new IllegalArgumentException("$name not specified/empty") + } } checkEmptiness(q.common, "query.common") checkEmptiness(q.common.studies, "query.common.studies") diff --git a/src/groovy/org/transmartproject/db/dataquery/RegionResultImpl.groovy b/src/groovy/org/transmartproject/db/dataquery/RegionResultImpl.groovy index 0712fefe..d347b04a 100644 --- a/src/groovy/org/transmartproject/db/dataquery/RegionResultImpl.groovy +++ b/src/groovy/org/transmartproject/db/dataquery/RegionResultImpl.groovy @@ -1,5 +1,6 @@ package org.transmartproject.db.dataquery +import groovy.transform.CompileStatic import org.hibernate.ScrollableResults import org.transmartproject.core.dataquery.acgh.RegionResult import org.transmartproject.core.dataquery.acgh.RegionRow @@ -7,7 +8,8 @@ import org.transmartproject.core.dataquery.assay.Assay import org.transmartproject.db.highdim.DeChromosomalRegion import org.transmartproject.db.highdim.DeSubjectAcghData -final class RegionResultImpl implements RegionResult, Closeable { +@CompileStatic +class RegionResultImpl implements RegionResult, Closeable { final List indicesList @@ -20,13 +22,14 @@ final class RegionResultImpl implements RegionResult, Closeable { this.results = results } - private RegionRow getNextRegionRow() { + protected RegionRow getNextRegionRow() { def entry = results.get() - if (entry == null) + if (entry == null) { return null + } - DeSubjectAcghData v = entry[0] - DeChromosomalRegion commonRegion = entry[1] + DeSubjectAcghData v = entry[0] as DeSubjectAcghData + DeChromosomalRegion commonRegion = entry[1] as DeChromosomalRegion Map values = new HashMap(indicesList.size()) /* Use .@ to access the field and bypass the getter. The problem is @@ -38,9 +41,10 @@ final class RegionResultImpl implements RegionResult, Closeable { while (v.@region.id == commonRegion.id) { values[v.@assay.id] = v - if (!results.next()) + if (!results.next()) { break - v = results.get()[0] + } + v = results.get()[0] as DeSubjectAcghData } new RegionRowImpl(commonRegion, indicesList, values) @@ -51,9 +55,9 @@ final class RegionResultImpl implements RegionResult, Closeable { def row = getNextRegionRow() [ - hasNext: { row != null }, - next: { def r = row; row = getNextRegionRow(); r }, - remove: { throw new UnsupportedOperationException() } + hasNext: { row != null }, + next: { def r = row; row = getNextRegionRow(); r }, + remove: { throw new UnsupportedOperationException() } ] as Iterator } diff --git a/src/groovy/org/transmartproject/db/dataquery/RegionRowImpl.groovy b/src/groovy/org/transmartproject/db/dataquery/RegionRowImpl.groovy index 078318f3..a467187a 100644 --- a/src/groovy/org/transmartproject/db/dataquery/RegionRowImpl.groovy +++ b/src/groovy/org/transmartproject/db/dataquery/RegionRowImpl.groovy @@ -1,10 +1,12 @@ package org.transmartproject.db.dataquery +import groovy.transform.CompileStatic import org.transmartproject.core.dataquery.acgh.ACGHValues import org.transmartproject.core.dataquery.acgh.Region import org.transmartproject.core.dataquery.acgh.RegionRow import org.transmartproject.core.dataquery.assay.Assay +@CompileStatic class RegionRowImpl implements RegionRow { final Region region @@ -37,11 +39,13 @@ class RegionRowImpl implements RegionRow { @Override ACGHValues getRegionDataForAssay(Assay assay) throws ArrayIndexOutOfBoundsException { - values[assay.id] ?: { - throw new ArrayIndexOutOfBoundsException("Assay with id $assay.id" + - " is not a valid index for this row; valid indexes are " + - "${values.keySet()}, which should match " + - assayList.collect { it.id }) - }() + ACGHValues result = values[assay.id] + if(!result) { + def ids = assayList.collect { Assay assayp -> assayp.id } + throw new ArrayIndexOutOfBoundsException("""Assay with id $assay.id + is not a valid index for this row; valid indexes are + ${values.keySet()}, which should match $ids""") + } + result } } diff --git a/test/integration/org/transmartproject/db/dataquery/DataQueryResourceServiceTests.groovy b/test/integration/org/transmartproject/db/dataquery/DataQueryResourceServiceTests.groovy index 28709061..3a4ed57e 100644 --- a/test/integration/org/transmartproject/db/dataquery/DataQueryResourceServiceTests.groovy +++ b/test/integration/org/transmartproject/db/dataquery/DataQueryResourceServiceTests.groovy @@ -3,11 +3,7 @@ package org.transmartproject.db.dataquery import com.google.common.collect.Iterables import com.google.common.collect.Lists import org.junit.Before -import org.junit.Rule import org.junit.Test -import org.junit.rules.ExpectedException -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner import org.transmartproject.core.dataquery.Patient import org.transmartproject.core.dataquery.Platform import org.transmartproject.core.dataquery.acgh.ACGHValues @@ -18,26 +14,22 @@ import org.transmartproject.core.dataquery.constraints.ACGHRegionQuery import org.transmartproject.core.dataquery.constraints.CommonHighDimensionalQueryConstraints import org.transmartproject.db.highdim.DeGplInfo import org.transmartproject.db.highdim.DeSubjectSampleMapping -import org.transmartproject.db.highdim.HighDimTestData import org.transmartproject.db.querytool.QtQueryResultInstance -import org.transmartproject.db.querytool.QueryResultData import static org.hamcrest.MatcherAssert.assertThat -import static org.junit.Assert.fail import static org.hamcrest.Matchers.* +import static org.junit.Assert.fail +import static org.transmartproject.test.Matchers.hasSameInterfaceProperties -@Mixin(HighDimTestData) -@Mixin(QueryResultData) -class DataQueryResourceServiceTests { +abstract class DataQueryResourceServiceTests { def sessionFactory - def dataQueryResourceService def resultInstance + def testedService @Before void setUp() { def queryMaster - assertThat testRegionPlatform.save(), isA(Platform) assertThat testRegions*.save(), everyItem(isA(Region)) assertThat testRegionPatients*.save(), everyItem(isA(Patient)) @@ -62,14 +54,14 @@ class DataQueryResourceServiceTests { patientQueryResult: resultInstance ), ) - def result = dataQueryResourceService.runACGHRegionQuery(q, null) + def result = testedService.runACGHRegionQuery(q, null) assertThat result, allOf( is(notNullValue()), hasProperty('indicesList', contains( /* they're ordered by assay id */ - equalTo(testRegionAssays[1]), - equalTo(testRegionAssays[0]), + hasSameInterfaceProperties(Assay, testRegionAssays[1], ['platform']), + hasSameInterfaceProperties(Assay, testRegionAssays[0], ['platform']), )) ) @@ -79,18 +71,18 @@ class DataQueryResourceServiceTests { assertThat regionRows, hasSize(2) /* results are ordered (asc) by region id */ assertThat regionRows[0], - hasProperty('region', equalTo(testRegions[1])) + hasProperty('region', hasSameInterfaceProperties(Region, testRegions[1], ['platform'])) assertThat regionRows[1], - hasProperty('region', equalTo(testRegions[0])) + hasProperty('region', hasSameInterfaceProperties(Region, testRegions[0], ['platform'])) assertThat regionRows[0].getRegionDataForAssay(testRegionAssays[0]), - equalTo(testACGHData[2]) + hasSameInterfaceProperties(ACGHValues, testACGHData[2]) assertThat regionRows[0].getRegionDataForAssay(testRegionAssays[1]), - equalTo(testACGHData[3]) + hasSameInterfaceProperties(ACGHValues, testACGHData[3]) assertThat regionRows[1].getRegionDataForAssay(testRegionAssays[0]), - equalTo(testACGHData[0]) + hasSameInterfaceProperties(ACGHValues, testACGHData[0]) assertThat regionRows[1].getRegionDataForAssay(testRegionAssays[1]), - equalTo(testACGHData[1]) + hasSameInterfaceProperties(ACGHValues, testACGHData[1]) } @Test @@ -114,35 +106,35 @@ class DataQueryResourceServiceTests { assays << a a = new DeSubjectSampleMapping([ - *:base, + *: base, trialName: 'TRIAL_NOT_MATCHING' ]) a.id = -4002 assays << a a = new DeSubjectSampleMapping([ - *:base, + *: base, timepointCd: 'non_match' ]) a.id = -4003 assays << a a = new DeSubjectSampleMapping([ - *:base, + *: base, sampleTypeCd: 'non_match' ]) a.id = -4004 assays << a a = new DeSubjectSampleMapping([ - *:base, + *: base, tissueTypeCd: 'non_match' ]) a.id = -4005 assays << a a = new DeSubjectSampleMapping([ - *:base, + *: base, platform: extraPlatform ]) a.id = -4006 @@ -151,7 +143,7 @@ class DataQueryResourceServiceTests { assertThat extraPlatform.save(), isA(Platform) assertThat assays*.save(), everyItem(isA(Assay)) - assertThat assays.collect { assay -> + assertThat assays.collect {assay -> createACGHData(testRegions[0], assay, -1) }*.save(), everyItem(isA(ACGHValues)) @@ -166,7 +158,7 @@ class DataQueryResourceServiceTests { ), ) - def result = dataQueryResourceService.runACGHRegionQuery(q, null) + def result = testedService.runACGHRegionQuery(q, null) assertThat result, hasProperty('indicesList', contains( hasProperty('id', equalTo(-4001L)) @@ -185,7 +177,7 @@ class DataQueryResourceServiceTests { QtQueryResultInstance, resultInstance.id) ), ) - def result = dataQueryResourceService.runACGHRegionQuery(q, session) + def result = testedService.runACGHRegionQuery(q, session) assertThat result, hasProperty('indicesList', hasSize(2)) def regionRows = Lists.newArrayList(result.rows) @@ -201,7 +193,7 @@ class DataQueryResourceServiceTests { ) try { - dataQueryResourceService.runACGHRegionQuery(q, null) + testedService.runACGHRegionQuery(q, null) fail("Expected exception") } catch (e) { assertThat(e, allOf( @@ -222,7 +214,7 @@ class DataQueryResourceServiceTests { ) try { - dataQueryResourceService.runACGHRegionQuery(q, null) + testedService.runACGHRegionQuery(q, null) fail("Expected exception") } catch (e) { assertThat(e, allOf( @@ -233,5 +225,4 @@ class DataQueryResourceServiceTests { )) } } - } diff --git a/test/integration/org/transmartproject/db/dataquery/GormDataQueryResourceServiceTests.groovy b/test/integration/org/transmartproject/db/dataquery/GormDataQueryResourceServiceTests.groovy new file mode 100644 index 00000000..3fb71053 --- /dev/null +++ b/test/integration/org/transmartproject/db/dataquery/GormDataQueryResourceServiceTests.groovy @@ -0,0 +1,17 @@ +package org.transmartproject.db.dataquery + +import org.transmartproject.db.highdim.HighDimTestData +import org.transmartproject.db.querytool.QueryResultData + +@Mixin(HighDimTestData) +@Mixin(QueryResultData) +class GormDataQueryResourceServiceTests extends DataQueryResourceServiceTests { + + def dataQueryResourceService + + @Override + void setUp() { + testedService = dataQueryResourceService + super.setUp() + } +} diff --git a/test/integration/org/transmartproject/db/dataquery/NoGormDataQueryResourceServiceTests.groovy b/test/integration/org/transmartproject/db/dataquery/NoGormDataQueryResourceServiceTests.groovy new file mode 100644 index 00000000..375bc1d1 --- /dev/null +++ b/test/integration/org/transmartproject/db/dataquery/NoGormDataQueryResourceServiceTests.groovy @@ -0,0 +1,17 @@ +package org.transmartproject.db.dataquery + +import org.transmartproject.db.highdim.HighDimTestData +import org.transmartproject.db.querytool.QueryResultData; + +@Mixin(HighDimTestData) +@Mixin(QueryResultData) +class NoGormDataQueryResourceServiceTests extends DataQueryResourceServiceTests { + + def dataQueryResourceNoGormService + + @Override + void setUp() { + testedService = dataQueryResourceNoGormService + super.setUp() + } +} diff --git a/test/integration/org/transmartproject/db/highdim/HighDimTestData.groovy b/test/integration/org/transmartproject/db/highdim/HighDimTestData.groovy index 849d2563..3a7553eb 100644 --- a/test/integration/org/transmartproject/db/highdim/HighDimTestData.groovy +++ b/test/integration/org/transmartproject/db/highdim/HighDimTestData.groovy @@ -3,7 +3,6 @@ package org.transmartproject.db.highdim import org.transmartproject.core.dataquery.acgh.Region import org.transmartproject.core.dataquery.assay.Assay import org.transmartproject.db.i2b2data.PatientDimension -import org.transmartproject.db.querytool.QtQueryResultInstance class HighDimTestData { diff --git a/test/integration/org/transmartproject/test/Matchers.groovy b/test/integration/org/transmartproject/test/Matchers.groovy new file mode 100644 index 00000000..d5f3a061 --- /dev/null +++ b/test/integration/org/transmartproject/test/Matchers.groovy @@ -0,0 +1,71 @@ +package org.transmartproject.test + +import org.codehaus.groovy.grails.commons.DomainClassArtefactHandler +import org.hamcrest.BaseMatcher +import org.hamcrest.Description; + +class Matchers { + //TODO Print failDetails in fail description + static class CompareInterfacePropertiesMatcher extends BaseMatcher { + + private Object value + private Class interf + private List excludes + private def failDetails = [] + + @Override + boolean matches(Object item) { + def interfacePropertyNames = interf.metaClass.properties*.name + def propertyMap = { + def ret = it.properties.findAll {k, v -> + interfacePropertyNames.contains(k) && + !excludes.contains(k) + } + //TODO Implement better way + if (DomainClassArtefactHandler.isDomainClass(it.getClass()) && + it.id != null) { + ret.id = it.id + } + ret + } + + if (interf.isAssignableFrom(item.class)) { + def itemPropertyMap = propertyMap(item) + def valuePropertyMap = propertyMap(value) + failDetails << value.properties + def diff1 = itemPropertyMap - valuePropertyMap + if (diff1) { + failDetails << "$item has different values than $value:\n$diff1" + } + + def diff2 = valuePropertyMap - itemPropertyMap + if (diff2) { + failDetails << "$value has different values than $item:\n$diff2" + } + + !(diff1 || diff2) + } else { + failDetails << "$interf is not assignable from ${item.class}" + false + } + } + + @Override + void describeTo(Description description) { + description.appendText("hasSameInterfaceProperties(") + .appendValue(interf.name).appendText(", ") + .appendValue(value.toString()).appendText(", ") + .appendValue(excludes.toString()).appendText(")") + } + } + + private static BaseMatcher hasSameInterfaceProperties(Class interf, + Object value, + List excludes = []) { + new CompareInterfacePropertiesMatcher( + value: value, + excludes: excludes, + interf: interf, + ) + } +}