├── src
├── test
│ ├── resources
│ │ └── config
│ │ │ └── application-integrationtest.yml
│ └── java
│ │ └── com
│ │ └── jfcorugedo
│ │ ├── rserver
│ │ ├── engine
│ │ │ ├── RConnectionFactoryTest.java
│ │ │ ├── REngineStdOutCallbackTest.java
│ │ │ ├── JRIEngineProviderServiceTest.java
│ │ │ ├── JRIEngineProviderServiceIT.java
│ │ │ ├── RServeEngineProviderServiceIT.java
│ │ │ └── RServeEngineProviderServiceTest.java
│ │ ├── generalization
│ │ │ └── algorithm
│ │ │ │ ├── KolmogorovSmirnovTestImplIT.java
│ │ │ │ ├── KolmogorovSmirnovTestImplTest.java
│ │ │ │ └── ChiSquareTestImplTest.java
│ │ ├── service
│ │ │ ├── RServiceImplIT.java
│ │ │ ├── RControllerTest.java
│ │ │ └── RServiceImplTest.java
│ │ └── common
│ │ │ └── collection
│ │ │ └── CollectionUtilsTest.java
│ │ └── sample
│ │ └── rjava
│ │ ├── SimpleArithmeticCalculationsIT.java
│ │ └── UseREngineInFrontOfJRIEngineIT.java
└── main
│ ├── java
│ └── com
│ │ └── jfcorugedo
│ │ └── rserver
│ │ ├── engine
│ │ ├── REngineException.java
│ │ ├── RConnectionFactory.java
│ │ ├── REngineStdOutCallback.java
│ │ ├── REngineProviderService.java
│ │ ├── JRIEngineProviderService.java
│ │ └── RServeEngineProviderService.java
│ │ ├── exception
│ │ ├── BadRequestException.java
│ │ └── DimensionMismatchException.java
│ │ ├── generalization
│ │ └── algorithm
│ │ │ ├── ChiSquareTest.java
│ │ │ ├── KolmogorovSmirnovTest.java
│ │ │ ├── KolmogorovSmirnovTestImpl.java
│ │ │ └── ChiSquareTestImpl.java
│ │ ├── Application.java
│ │ ├── service
│ │ ├── RController.java
│ │ ├── RService.java
│ │ └── RServiceImpl.java
│ │ ├── common
│ │ └── collection
│ │ │ └── CollectionUtils.java
│ │ └── metrics
│ │ └── DatadogReporterConfig.java
│ └── resources
│ ├── config
│ ├── application-local.yml
│ └── application.yml
│ └── logback.xml
├── .buildpacks
├── Procfile
├── .gitignore
├── rserve.conf
├── rserve_local.conf
├── init.r
├── .travis.yml
├── LICENSE
├── blockFunction.r
├── README.md
└── pom.xml
/src/test/resources/config/application-integrationtest.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 0
--------------------------------------------------------------------------------
/.buildpacks:
--------------------------------------------------------------------------------
1 | http://github.com/virtualstaticvoid/heroku-buildpack-r.git#cedar-14
2 | https://github.com/heroku/heroku-buildpack-java.git
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: $JAVA_HOME/bin/java -Dserver.port=$PORT -Dspring.profiles.active=$SPRING_PROFILES_ACTIVE -Djava.library.path=/app/vendor/R/lib/R/library/rJava/jri/ -jar target/rjavaserver.jar
2 |
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | pom.xml.tag
3 | pom.xml.releaseBackup
4 | pom.xml.versionsBackup
5 | pom.xml.next
6 | release.properties
7 | dependency-reduced-pom.xml
8 | buildNumber.properties
9 | /.springBeans
10 | /.factorypath
11 |
--------------------------------------------------------------------------------
/rserve.conf:
--------------------------------------------------------------------------------
1 | # See http://www.rforge.net/Rserve/doc.html#conf for configuration details
2 |
3 | remote enable
4 | auth disable
5 | #We can also load R script here
6 | source /app/blockFunction.r
7 | encoding utf8
8 | interactive no
9 |
--------------------------------------------------------------------------------
/rserve_local.conf:
--------------------------------------------------------------------------------
1 | # See http://www.rforge.net/Rserve/doc.html#conf for configuration details
2 |
3 | remote enable
4 | auth disable
5 | #We can also load R script here
6 | source /Users/jfcorugedo/Documents/git/kmd/kmd-math/blockFunction.r
7 | encoding utf8
8 | interactive no
9 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/engine/REngineException.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | public class REngineException extends RuntimeException{
4 |
5 | private static final long serialVersionUID = 1L;
6 |
7 | public REngineException(String message, Throwable cause) {
8 | super(message, cause);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/resources/config/application-local.yml:
--------------------------------------------------------------------------------
1 | rJavaServer:
2 | rengine:
3 | blockFunction: source("/Users/jfcorugedo/Documents/git/kmd/kmd-math/blockFunction.R")
4 | rserveConf: /Users/jfcorugedo/Documents/git/RJavaServer/rserve_local.conf
5 | rexe: /Library/Frameworks/R.framework/Versions/3.2/Resources/bin/exec/R
6 | enableJRI: false
7 | metrics:
8 | period: 10
--------------------------------------------------------------------------------
/src/main/resources/config/application.yml:
--------------------------------------------------------------------------------
1 | endpoints:
2 | shutdown:
3 | enabled: true
4 | beans:
5 | sensitive: true
6 | server:
7 | port: 8080
8 | rJavaServer:
9 | rengine:
10 | blockFunction: source("/app/blockFunction.R")
11 | rserveConf: /app/rserve.conf
12 | rexe: /app/vendor/R/lib/R/bin/R
13 | metrics:
14 | apiKey: cf72a3a58261d378f033fb6b842a8601
15 | host: rJavaServer.dev
16 | period: 20
17 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/exception/BadRequestException.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.exception;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | @ResponseStatus(value=HttpStatus.BAD_REQUEST)
7 | public class BadRequestException extends RuntimeException {
8 |
9 | private static final long serialVersionUID = 1L;
10 |
11 | public BadRequestException(String message) {
12 | super(message);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/exception/DimensionMismatchException.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.exception;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | @ResponseStatus(value=HttpStatus.BAD_REQUEST)
7 | public class DimensionMismatchException extends RuntimeException {
8 |
9 | private static final long serialVersionUID = 1L;
10 |
11 |
12 | public DimensionMismatchException(String message) {
13 | super(message);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/init.r:
--------------------------------------------------------------------------------
1 | #
2 | # Example R code to install packages
3 | # See http://cran.r-project.org/doc/manuals/R-admin.html#Installing-packages for details
4 | #
5 |
6 | ###########################################################
7 | # Update this line with the R packages to install:
8 |
9 | my_packages = c("blockTools","rJava","Rserve","plyr")
10 |
11 | ###########################################################
12 |
13 | install_if_missing = function(p) {
14 | if (p %in% rownames(installed.packages()) == FALSE) {
15 | install.packages(p, dependencies = TRUE)
16 | }
17 | else {
18 | cat(paste("Skipping already installed package:", p, "\n"))
19 | }
20 | }
21 | invisible(sapply(my_packages, install_if_missing))
22 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/engine/RConnectionFactoryTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import static org.mockito.Mockito.mock;
4 | import static org.mockito.Mockito.times;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.Test;
8 | import org.rosuda.REngine.REngine;
9 |
10 | public class RConnectionFactoryTest {
11 |
12 | private RConnectionFactory connectionFactory = new RConnectionFactory();
13 |
14 | @Test
15 | public void releaseConnectionTriesToCloseConnection() {
16 |
17 | REngine engine = mock(REngine.class);
18 |
19 | connectionFactory.releaseConnection(engine);
20 |
21 | verify(engine, times(1)).close();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/engine/RConnectionFactory.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import org.rosuda.REngine.REngine;
4 | import org.rosuda.REngine.Rserve.RConnection;
5 | import org.rosuda.REngine.Rserve.RserveException;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.stereotype.Component;
9 |
10 | @Component
11 | public class RConnectionFactory {
12 |
13 | private static final Logger LOGGER = LoggerFactory.getLogger(RConnectionFactory.class);
14 |
15 | public RConnection getConnection() throws RserveException {
16 | return new RConnection();
17 | }
18 |
19 | public void releaseConnection(REngine engine) {
20 | if(engine != null && !engine.close()) {
21 | LOGGER.warn("Unexpected error closing RServe connection");
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 | script: mvn -B verify
5 | after_success: mvn coveralls:report
6 | deploy:
7 | provider: heroku
8 | app: rjavaserver
9 | api_key:
10 | secure: HbF8N92OYm1Qe40ZzJrHoujblIbx7LIAzd1MlSS4CV6SPEVEtHJ0ee6u/zOUeJBBgvE4/6tiuq0bFu4pcBamYofgRoFmpVB5vwOVl/bqeFx/3vohkpgs0IVSlvGCH/bUW/yCwP58zt1UhzLztMigKwRJFj0vczDcBOLaNdVdeXRFTutnBcPcEUHt5umP4i4tNpm7HLv7gQrg5Xc524k98WKdz+RiTSUDVpFRfCYvFZ06hB8DDQgvsTCmvtXdpHDjvj38z2iU0xN0xyCziYrWGCZHvot5nnKCxm2j5snKSEnWS5iLOZh72tfyFQ3jiDBWXOiz+kQgX5/3OzZ1XdFWxM3l2w0JtG6fgtFaLkz2OSr8jRf9jjKXAD0/NeeFlpy3JtT0wJbN7ozeAFLn5n7wYikhLVhGvcwXlQeRR0OEUMRfifvebRg3kAOlUsTE6iZf8cZC8ZiVkq3i7wdRb4ZGdYGmAZE6wNHSsWb79I697jMKusUM3e6+1RPJvYFNPGlylFeqH4EsL3DaXfa7d1nA6XWmOR6z0rH7Y7uLc/ePDNTX90ZE88RxvWnx6XZnpNCbcqwOhLh8Kcv9jaE1Cao7BMeQDkKRm0lGNLDNU2Nu08Yqks8T+D+x1DiIGiy8CqrsibYZLGOn9/fma/cm63xw3Y1DVeS6eeOb+bufHOszqrk=
11 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/generalization/algorithm/ChiSquareTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.generalization.algorithm;
2 |
3 | /**
4 | * This component executes Chi-Square test over two samples in order to compare them.
5 | *
6 | *
7 | * @author jfcorugedo
8 | *
9 | */
10 | public interface ChiSquareTest {
11 |
12 | double DEFAULT_CONFIDENCE_LEVEL = 0.05;
13 |
14 | /**
15 | * This test evaluates the null hypothesis that the two list of observed counts
16 | * conform to the same frequency distribution.
17 | *
18 | * @param observed1
19 | * @param observed2
20 | * @return true if the null hypothesis can be accepted
21 | */
22 | boolean areGeneralizable(long[] observed1, long[] observed2);
23 |
24 | /**
25 | * It returns a string representation of the algorithm used to compare the given samples.
26 | *
27 | * @return
28 | */
29 | String getAlgorithm();
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/engine/REngineStdOutCallback.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import org.rosuda.REngine.REngine;
4 | import org.rosuda.REngine.REngineCallbacks;
5 | import org.rosuda.REngine.REngineOutputInterface;
6 | import org.slf4j.Logger;
7 |
8 | public class REngineStdOutCallback implements REngineCallbacks, REngineOutputInterface{
9 |
10 | private Logger logger;
11 |
12 | public REngineStdOutCallback(Logger logger) {
13 | this.logger = logger;
14 | }
15 |
16 | @Override
17 | public void RWriteConsole(REngine eng, String text, int oType) {//NOSONAR
18 | if(oType == 0) {
19 | logger.info(text);
20 | } else {
21 | logger.warn(text);
22 | }
23 | }
24 |
25 | @Override
26 | public void RShowMessage(REngine eng, String text) {//NOSONAR
27 | logger.error(text);
28 | }
29 |
30 | @Override
31 | public void RFlushConsole(REngine eng) {//NOSONAR
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/generalization/algorithm/KolmogorovSmirnovTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.generalization.algorithm;
2 |
3 | /**
4 | * This component executes Two-sample Kolmogorov–Smirnov test over two samples in order to
5 | * know if both of them are equivalent and generalizable
6 | *
7 | * @see https://en.wikipedia.org/wiki/Kolmogorov–Smirnov_test
8 | * @author jfcorugedo
9 | *
10 | */
11 | public interface KolmogorovSmirnovTest {
12 |
13 | double CRITICAL_VALUE_0_05 = 1.36;
14 | double CRITICAL_VALUE_0_01 = 1.63;
15 |
16 | /**
17 | * This test try to test if the null hypothesis is truth or not.
18 | *
19 | * The null hypothesis is that the samples are drawn from the same distribution.
20 | *
21 | */
22 | boolean areGeneralizable(double[] x, double[] y);
23 |
24 | /**
25 | * It returns a string representation of the algorithm used to compare the given samples.
26 | *
27 | * @return
28 | */
29 | String getAlgorithm();
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Juan Fernandez-Corugedo Igual
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/generalization/algorithm/KolmogorovSmirnovTestImpl.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.generalization.algorithm;
2 |
3 | import java.util.Arrays;
4 |
5 | import javax.inject.Inject;
6 |
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.stereotype.Component;
10 |
11 | import com.codahale.metrics.annotation.Timed;
12 | import com.jfcorugedo.rserver.service.RService;
13 |
14 | @Component
15 | public class KolmogorovSmirnovTestImpl implements KolmogorovSmirnovTest {
16 |
17 | private static final Logger LOGGER = LoggerFactory.getLogger(KolmogorovSmirnovTestImpl.class);
18 |
19 | /** Name of the algorithm used to compare two samples */
20 | private static final String KS_TEST = "K-S test";
21 |
22 | @Inject
23 | private RService rService;
24 |
25 | /**
26 | * This test try to test if the null hypothesis is truth or not.
27 | *
28 | * The null hypothesis is that the samples are drawn from the same distribution.
29 | *
30 | */
31 | @Override
32 | @Timed
33 | public boolean areGeneralizable(double[] x, double[] y){
34 |
35 | if(LOGGER.isDebugEnabled()) {
36 | LOGGER.debug("Calculating K-S test of \nx: {}\ny: {}", Arrays.toString(x), Arrays.toString(y));
37 | }
38 |
39 | double pValue = rService.kolmogorovSmirnovTest(x, y);
40 |
41 | if(LOGGER.isDebugEnabled()) {
42 | LOGGER.debug("K-S pValue calculated: {}", pValue);
43 | }
44 |
45 | //Accept null hypothesis: Both samples come from the same distribution
46 | return pValue > 0.05;
47 | }
48 |
49 | @Override
50 | public String getAlgorithm() {
51 |
52 | return KS_TEST;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/engine/REngineStdOutCallbackTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import static org.mockito.Matchers.anyString;
4 | import static org.mockito.Mockito.mock;
5 | import static org.mockito.Mockito.never;
6 | import static org.mockito.Mockito.times;
7 | import static org.mockito.Mockito.verify;
8 |
9 | import org.junit.Test;
10 | import org.rosuda.REngine.REngine;
11 | import org.slf4j.Logger;
12 |
13 | public class REngineStdOutCallbackTest {
14 |
15 | @Test
16 | public void testPrintInfoMessage() {
17 |
18 | Logger loggerMock = mock(Logger.class);
19 | REngineStdOutCallback stdoutCallback = new REngineStdOutCallback(loggerMock);
20 |
21 | stdoutCallback.RWriteConsole(mock(REngine.class), "Something has happened", 0);
22 |
23 | verify(loggerMock, times(1)).info("Something has happened");
24 | }
25 |
26 | @Test
27 | public void testPrintWarnMessage() {
28 |
29 | Logger loggerMock = mock(Logger.class);
30 | REngineStdOutCallback stdoutCallback = new REngineStdOutCallback(loggerMock);
31 |
32 | stdoutCallback.RWriteConsole(mock(REngine.class), "Something goes wrong", 1);
33 |
34 | verify(loggerMock, times(1)).warn("Something goes wrong");
35 | }
36 |
37 | @Test
38 | public void testPrintErrorMessage() {
39 |
40 | Logger loggerMock = mock(Logger.class);
41 | REngineStdOutCallback stdoutCallback = new REngineStdOutCallback(loggerMock);
42 |
43 | stdoutCallback.RShowMessage(mock(REngine.class), "Something goes really wrong");
44 |
45 | verify(loggerMock, times(1)).error("Something goes really wrong");
46 | }
47 |
48 | @Test
49 | public void testFlushConsole() {
50 |
51 | Logger loggerMock = mock(Logger.class);
52 | REngineStdOutCallback stdoutCallback = new REngineStdOutCallback(loggerMock);
53 |
54 | stdoutCallback.RFlushConsole(mock(REngine.class));
55 |
56 | verify(loggerMock, never()).info(anyString());
57 | verify(loggerMock, never()).warn(anyString());
58 | verify(loggerMock, never()).error(anyString());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/Application.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.boot.SpringApplication;
6 | import org.springframework.boot.autoconfigure.SpringBootApplication;
7 | import org.springframework.core.env.SimpleCommandLinePropertySource;
8 |
9 | import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;
10 |
11 | @SpringBootApplication
12 | @EnableMetrics
13 | public class Application {
14 |
15 | private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
16 |
17 | public static void main(String[] args) {
18 | LOGGER.info("\n****************\n* rJava Server *\n****************\n");
19 |
20 | SpringApplication app = new SpringApplication(Application.class);
21 | app.setShowBanner(false);
22 | SimpleCommandLinePropertySource source = new SimpleCommandLinePropertySource(args);
23 |
24 | // Check if the selected profile has been set as argument.
25 | // if not the development profile will be added
26 | addDefaultProfile(app, source);
27 |
28 |
29 |
30 | app.run(args);
31 | }
32 |
33 | /**
34 | * Set a default profile if it has not been set
35 | */
36 | private static void addDefaultProfile(SpringApplication app, SimpleCommandLinePropertySource source) {
37 | if (!source.containsProperty("spring.profiles.active")) {
38 | app.setAdditionalProfiles("local");
39 | LOGGER.info("Staring application with profiles: local");
40 | } else {
41 | LOGGER.info("Staring application with profiles: {}", source.getProperty("spring.profiles.active"));
42 | }
43 | }
44 |
45 | /**
46 | * This method is needed because Sonar doesn't allow public constructors in a class with only static methods,
47 | * but SpringBoot needs a public constructor in this class.
48 | * This method should not be invoked
49 | */
50 | public void avoidSonarViolation(){
51 | LOGGER.warn("This method should not be invoked");
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/generalization/algorithm/ChiSquareTestImpl.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.generalization.algorithm;
2 |
3 | import static java.lang.String.format;
4 |
5 | import org.springframework.stereotype.Component;
6 |
7 | import com.codahale.metrics.annotation.Timed;
8 | import com.jfcorugedo.rserver.exception.DimensionMismatchException;
9 |
10 | @Component
11 | public class ChiSquareTestImpl implements ChiSquareTest {
12 |
13 | public static final String CHI_TEST = "Chi-Square test";
14 |
15 | private org.apache.commons.math3.stat.inference.ChiSquareTest chiTest = new org.apache.commons.math3.stat.inference.ChiSquareTest();
16 |
17 | @Override
18 | @Timed
19 | public boolean areGeneralizable(long[] observed1, long[] observed2) {
20 |
21 | if(observed1.length != observed2.length) {
22 | throw new DimensionMismatchException(format("Observed samples have different dimension: %d != %d", observed1.length, observed2.length));
23 | }
24 |
25 | if(isBinary(observed1, observed2) && (observed1[0] == 0 || observed1[1] == 0)){
26 | return true;
27 | }
28 |
29 | return !chiTest.chiSquareTestDataSetsComparison(observed1, observed2, DEFAULT_CONFIDENCE_LEVEL);
30 | }
31 |
32 | /**
33 | * it returns true when there are only two possible values on each sample.
34 | *
35 | * For instance, given two samples with these samples:
36 | *
37 | *
38 | * - "true":10, "false":20
39 | * - "true":2, "false":3
40 | *
41 | *
42 | * This method will return true, because there are only two possible values: "true" or "false".
43 | *
44 | * On the other hand, given these samples:
45 | *
46 | *
47 | * - "MAD":10, "LON":23, "NY":45
48 | * - "MAD":2, "LON":4, "NY":7
49 | *
50 | *
51 | * This method will return flase, because there three possible values in each sample
52 | *
53 | * @param observed1
54 | * @param observed2
55 | * @return
56 | */
57 | private boolean isBinary(long[] observed1, long[] observed2) {
58 |
59 | return observed1.length == 2 && observed2.length == 2;
60 | }
61 |
62 | @Override
63 | public String getAlgorithm() {
64 |
65 | return CHI_TEST;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/generalization/algorithm/KolmogorovSmirnovTestImplIT.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.generalization.algorithm;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.util.stream.IntStream;
6 |
7 | import javax.inject.Inject;
8 |
9 | import org.junit.Ignore;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.springframework.boot.test.SpringApplicationConfiguration;
13 | import org.springframework.boot.test.WebIntegrationTest;
14 | import org.springframework.test.context.ActiveProfiles;
15 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
16 |
17 | import com.jfcorugedo.rserver.Application;
18 |
19 | @Ignore("These tests requires R environment installed")
20 | @RunWith(SpringJUnit4ClassRunner.class)
21 | @SpringApplicationConfiguration(classes = Application.class)
22 | @WebIntegrationTest
23 | @ActiveProfiles(profiles={"local", "integrationtest"})
24 | public class KolmogorovSmirnovTestImplIT {
25 |
26 | @Inject
27 | private KolmogorovSmirnovTest ksTest;
28 |
29 | @Test
30 | public void areTwoEqualSamplesGeneralizable() {
31 |
32 | double[] sample = new double[]{1.0, 2.0, 3.0, 4.0};
33 |
34 | boolean result = ksTest.areGeneralizable(sample, sample);
35 |
36 | assertThat(result).isTrue();
37 | }
38 |
39 | @Test
40 | public void areTwoDifferentSamplesGeneralizable() {
41 |
42 | double[] sample = new double[]{1.0, 2.0, 3.0, 4.0};
43 | double[] different = new double[]{-1.0, -2.0, -3.0, -4.0};
44 |
45 | boolean result = ksTest.areGeneralizable(sample, different);
46 |
47 | assertThat(result).isFalse();
48 | }
49 |
50 | @Test
51 | public void getAlgorithmName() {
52 |
53 | String algorithm = ksTest.getAlgorithm();
54 |
55 | assertThat(algorithm).isEqualTo("K-S test");
56 | }
57 |
58 | @Test
59 | public void testSameLargeSamples() {
60 |
61 | double[] sample1 = IntStream.range(0, 10000).asDoubleStream().toArray();
62 | double[] sample2 = IntStream.range(0, 10000).asDoubleStream().toArray();
63 |
64 | boolean isGeneralizable = ksTest.areGeneralizable(sample1, sample2);
65 |
66 | assertThat(isGeneralizable).isTrue();
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/generalization/algorithm/KolmogorovSmirnovTestImplTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.generalization.algorithm;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.mockito.Matchers.any;
5 | import static org.mockito.Mockito.when;
6 |
7 | import java.util.stream.IntStream;
8 |
9 | import org.junit.Rule;
10 | import org.junit.Test;
11 | import org.mockito.InjectMocks;
12 | import org.mockito.Mock;
13 | import org.mockito.junit.MockitoJUnit;
14 | import org.mockito.junit.MockitoRule;
15 |
16 | import com.jfcorugedo.rserver.service.RService;
17 |
18 | public class KolmogorovSmirnovTestImplTest {
19 |
20 | @Rule
21 | public MockitoRule rule = MockitoJUnit.rule();
22 |
23 | @Mock
24 | private RService rService;
25 |
26 | @InjectMocks
27 | private KolmogorovSmirnovTestImpl ksTest = new KolmogorovSmirnovTestImpl();
28 |
29 | @Test
30 | public void areTwoEqualSamplesGeneralizable() {
31 |
32 | setKSPValue(1.0);
33 |
34 | double[] sample = new double[]{1.0, 2.0, 3.0, 4.0};
35 |
36 | boolean result = ksTest.areGeneralizable(sample, sample);
37 |
38 | assertThat(result).isTrue();
39 | }
40 |
41 | @Test
42 | public void areTwoDifferentSamplesGeneralizable() {
43 |
44 | setKSPValue(0.03);
45 |
46 | double[] sample = new double[]{1.0, 2.0, 3.0, 4.0};
47 | double[] different = new double[]{-1.0, -2.0, -3.0, -4.0};
48 |
49 | boolean result = ksTest.areGeneralizable(sample, different);
50 |
51 | assertThat(result).isFalse();
52 | }
53 |
54 | @Test
55 | public void getAlgorithmName() {
56 |
57 | String algorithm = ksTest.getAlgorithm();
58 |
59 | assertThat(algorithm).isEqualTo("K-S test");
60 | }
61 |
62 | @Test
63 | public void testSameSamples() {
64 |
65 | setKSPValue(0.03);
66 |
67 | double[] sample1 = IntStream.range(0, 100).asDoubleStream().toArray();
68 | double[] sample2 = IntStream.range(0, 100).asDoubleStream().toArray();
69 |
70 | boolean isGeneralizable = ksTest.areGeneralizable(sample1, sample2);
71 |
72 | assertThat(isGeneralizable).isFalse();
73 | }
74 |
75 | private void setKSPValue(double pValue) {
76 |
77 | when(rService.kolmogorovSmirnovTest(any(double[].class), any(double[].class))).thenReturn(pValue);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/engine/REngineProviderService.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import java.util.List;
4 |
5 | import org.rosuda.REngine.REXP;
6 | import org.rosuda.REngine.REXPDouble;
7 | import org.rosuda.REngine.REXPInteger;
8 | import org.rosuda.REngine.REXPString;
9 |
10 | /**
11 | * Interface used to obtain access to the low-level engine
12 | */
13 | public interface REngineProviderService {
14 |
15 | /**
16 | * Executes R block function against a list of values.
17 | *
18 | * @see https://cran.r-project.org/web/packages/blockTools/blockTools.pdf
19 | * @param ids Identifiers associated to each value
20 | * @param values Values used to perform the matching
21 | * @return An object containing all the IDs grouped in pairs based on the distance of each one
22 | */
23 | REXP blockFunction(REXPInteger ids, REXPDouble... values);
24 |
25 | /**
26 | * Executes R block function against a list of discrete (non-numeric) values.
27 | *
28 | * @see https://cran.r-project.org/web/packages/blockTools/blockTools.pdf
29 | * @param ids Identifiers associated to each value
30 | * @param values Values that must be grouped into pairs
31 | * @return An object containing all the IDs grouped in pairs based on the distance of eachone
32 | */
33 | REXP blockDiscreteFunction(REXPInteger ids, REXPString... values);
34 |
35 | /**
36 | * Executes R block function against a list of discrete (non-numeric) and continuous values.
37 | *
38 | * @see https://cran.r-project.org/web/packages/blockTools/blockTools.pdf
39 | * @param ids Identifiers associated to each value
40 | * @param discreteValues List of discrete values that must be used to group elements
41 | * @param continuousValues List of continuous values that must be used to group elements
42 | * @return An object containing all the IDs grouped in pairs based on the distance of eachone
43 | */
44 | REXP blockGeneralFunction(REXPInteger ids, List discreteValues, List continuousValues);
45 |
46 | /**
47 | * Calculates the sqare root of a given number
48 | * @param number
49 | * @return
50 | */
51 | double sqrt(double number);
52 |
53 | /**
54 | * Perform a two-sample Kolmogorov-Smirnov test.
55 | *
56 | * @param x A numeric vector of data values.
57 | * @param y A numeric vector of data values.
58 | * @return p-value associated with the null hypothesis that x and y come from the same distribution (are equivalent)
59 | */
60 | double ksTest(REXPDouble x, REXPDouble y);
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/sample/rjava/SimpleArithmeticCalculationsIT.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.sample.rjava;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.assertj.core.api.Assertions.fail;
5 |
6 | import org.junit.BeforeClass;
7 | import org.junit.Ignore;
8 | import org.junit.Test;
9 | import org.rosuda.JRI.REXP;
10 | import org.rosuda.JRI.Rengine;
11 |
12 | /**
13 | * In order to make these test work, two parameters must be configured:
14 | *
15 | * Inside VM args, add:
16 | * -Djava.library.path=/Library/Frameworks/R.framework/Versions/3.2/Resources/library/rJava/jri/
17 | *
18 | * Export an environment variable with this value:
19 | *
20 | * R_HOME=/Library/Frameworks/R.framework/Resources
21 | *
22 | * @author jfcorugedo
23 | *
24 | */
25 | @Ignore("Maven can't execute this tests because they require a R environmet installed")
26 | public class SimpleArithmeticCalculationsIT {
27 |
28 | private static Rengine engine = null;
29 |
30 | @BeforeClass
31 | public static void setUpR() {
32 | // just making sure we have the right version of everything
33 | if (!Rengine.versionCheck()) {
34 | System.err.println("** Version mismatch - Java files don't match library version.");
35 | fail(String.format("Invalid versions. Rengine must have the same version of native library. Rengine version: %d. RNI library version: %d", Rengine.getVersion(), Rengine.rniGetVersion()));
36 | }
37 |
38 | // Enables debug traces
39 | Rengine.DEBUG = 1;
40 |
41 | System.out.println("Creating Rengine (with arguments)");
42 | // 1) we pass the arguments from the command line
43 | // 2) we won't use the main loop at first, we'll start it later
44 | // (that's the "false" as second argument)
45 | // 3) no callback class will be used
46 | engine = new Rengine(new String[] { "--no-save" }, false, null);
47 | System.out.println("Rengine created, waiting for R");
48 | // the engine creates R is a new thread, so we should wait until it's
49 | // ready
50 | if (!engine.waitForR()) {
51 | fail("Cannot load R");
52 | }
53 | }
54 |
55 | @Test
56 | public void testMean() throws Exception{
57 |
58 |
59 | /*
60 | * High-level API - do not use RNI methods unless there is no other way
61 | * to accomplish what you want
62 | */
63 | engine.eval("rVector=c(1,2,3,4,5)");
64 | REXP result = engine.eval("meanVal=mean(rVector)");
65 | // generic vectors are RVector to accomodate names
66 | assertThat(result.asDouble()).isEqualTo(3.0);
67 | }
68 |
69 | @Test
70 | public void testSqrt() {
71 | REXP result = engine.eval("sqrt(36)");
72 | assertThat(result.asDouble()).isEqualTo(6.0);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/service/RController.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.service;
2 |
3 | import static org.springframework.web.bind.annotation.RequestMethod.POST;
4 |
5 | import java.util.List;
6 |
7 | import javax.inject.Inject;
8 |
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.http.HttpStatus;
12 | import org.springframework.http.ResponseEntity;
13 | import org.springframework.web.bind.annotation.RequestBody;
14 | import org.springframework.web.bind.annotation.RequestMapping;
15 | import org.springframework.web.bind.annotation.RestController;
16 |
17 | import com.codahale.metrics.annotation.Timed;
18 | import com.jfcorugedo.rserver.exception.BadRequestException;
19 | import com.jfcorugedo.rserver.generalization.algorithm.KolmogorovSmirnovTest;
20 |
21 |
22 | @RestController
23 | public class RController {
24 |
25 | public static final Logger LOGGER = LoggerFactory.getLogger(RController.class);
26 |
27 | @Inject
28 | private RService rService;
29 |
30 | @Inject
31 | private KolmogorovSmirnovTest ksTest;
32 |
33 | @RequestMapping(value="/stratification/continuous/group", method=POST)
34 | public ResponseEntity> block(@RequestBody(required=false) List data) {
35 | if(data != null) {
36 | return new ResponseEntity<>(rService.groupValues(data), HttpStatus.CREATED);
37 | } else {
38 | throw new BadRequestException("This service needs all the values of the stratification variable");
39 | }
40 | }
41 |
42 | @RequestMapping(value="/stratification/discrete/group", method=POST)
43 | public ResponseEntity> blockDiscrete(@RequestBody(required=false) List data) {
44 | if(data != null) {
45 | return new ResponseEntity<>(rService.groupDiscreteValues(data), HttpStatus.CREATED);
46 | } else {
47 | throw new BadRequestException("This service needs all the values of the stratification variable");
48 | }
49 | }
50 |
51 | @RequestMapping(value="/ks-test", method=POST)
52 | @Timed
53 | public ResponseEntity areGeneralizable(@RequestBody List values) {
54 |
55 | if(values.size() == 2) {
56 | boolean areGeneralizable = ksTest.areGeneralizable(values.get(0), values.get(1));
57 |
58 | return new ResponseEntity<>(String.format("%s: %b", ksTest.getAlgorithm(), areGeneralizable), HttpStatus.CREATED);
59 | } else {
60 | return new ResponseEntity<>("This algorithm needs two different list of values", HttpStatus.BAD_REQUEST);
61 | }
62 | }
63 |
64 | @RequestMapping(value="/sqrt", method=POST)
65 | public ResponseEntity sqrt(@RequestBody Double number) {
66 |
67 | return new ResponseEntity<>(rService.sqrt(number), HttpStatus.CREATED);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/service/RService.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.service;
2 |
3 | import java.util.List;
4 |
5 |
6 | public interface RService {
7 |
8 | /**
9 | * Groups the given values in pairs based on the correlation between values.
10 | *
11 | * For instance, given the following values:
12 | *
13 | * {1.0, 2.0, 2.0, 1.0}
14 | *
15 | * This function will return
16 | *
17 | * [{0,3}, {1,2}]
18 | *
19 | * So the first element in the values array must be paired with the fourth element of the array.
20 | * Similarly, the second element of the array must be paired with the third element.
21 | *
22 | * @param values Values to be paired
23 | * @return List of pairs. Each element in the list will have to indexes that point at the elements
24 | * of the input array that must be paired
25 | */
26 | List groupValues(List values);
27 |
28 | /**
29 | * Groups the given values in pairs based on the correlation between values.
30 | *
31 | * For instance, given the following values:
32 | *
33 | * {"A", "B", "B", "A"}
34 | *
35 | * This function will return
36 | *
37 | * [{0,3}, {1,2}]
38 | *
39 | * So the first element in the values array must be paired with the fourth element of the array.
40 | * Similarly, the second element of the array must be paired with the third element.
41 | *
42 | * @param values Values to be paired
43 | * @return List of pairs. Each element in the list will have to indexes that point at the elements
44 | * of the input array that must be paired
45 | */
46 | List groupDiscreteValues(List values);
47 |
48 | /**
49 | * Groups the given values in pairs based on the correlation between values.
50 | *
51 | * For instance, given the following values:
52 | *
53 | * {1.0, 2.0, 3.0, 4.0}, {"A", "A", "B", "B"}
54 | *
55 | * This function will return
56 | *
57 | * [{0,3}, {1,2}]
58 | *
59 | * So the first element in the values array must be paired with the fourth element of the array.
60 | * Similarly, the second element of the array must be paired with the third element.
61 | *
62 | * @param values Values to be paired
63 | * @return List of pairs. Each element in the list will have to indexes that point at the elements
64 | * of the input array that must be paired
65 | */
66 | List groupMultipleValues(List discreteValues, List continuousValues);
67 |
68 | /**
69 | * Computes the p-value, or observed significance level, of a two-sample Kolmogorov-Smirnov test
70 | * evaluating the null hypothesis that x and y are samples drawn from the same probability distribution.
71 | *
72 | * @param x
73 | * @param y
74 | * @return
75 | */
76 | double kolmogorovSmirnovTest(double[] x, double[] y);
77 |
78 |
79 | /**
80 | * This method returns the square root of the given value
81 | * @param number
82 | * @return
83 | */
84 | double sqrt(double number);
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/common/collection/CollectionUtils.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.common.collection;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Collection;
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.stream.Collectors;
9 |
10 | public final class CollectionUtils {
11 |
12 | private CollectionUtils(){
13 |
14 | }
15 |
16 | public static List arrayToList(T[] array){
17 | return Arrays.stream(array).collect(Collectors.toList());//NOSONAR: This stream doesn't need to be closed
18 | }
19 |
20 | /**
21 | * Return true if this map is null or it's empty.
22 | *
23 | * @param map
24 | * @return
25 | */
26 | public static boolean isEmpty(Map extends Object, ? extends Object> map) {
27 | return map == null || map.isEmpty();
28 | }
29 |
30 | public static boolean isEmpty(List extends Object> list) {
31 | return list == null || list.isEmpty();
32 | }
33 |
34 | /**
35 | * Return true if this map contains one value at least
36 | *
37 | * @param map
38 | * @return
39 | */
40 | public static boolean isNotEmpty(Map extends Object, ? extends Object> map) {
41 | return !isEmpty(map);
42 | }
43 |
44 | public static boolean isNotEmpty(Collection extends Object> collection) {
45 | return collection != null && !collection.isEmpty();
46 | }
47 |
48 | /**
49 | * Converts the given list of Double objects into an array of primitive doubles.
50 | *
51 | * If the list is null or empty, an empty array will be returned
52 | *
53 | * @param inputList List of Double object to be converted in a primitive array
54 | * @return
55 | */
56 | public static double[] listToPrimitiveArray(List inputList) {
57 | if(isEmpty(inputList)) {
58 | return new double[0];
59 | } else {
60 | return inputList.stream().mapToDouble(Double::new).toArray();
61 | }
62 | }
63 |
64 | /**
65 | * This method creates a new list and store in it all the objects contained in the
66 | * given list.
67 | *
68 | * @param targetList Source list used to create a new one
69 | * @return New list containing all the objects of the primitive list
70 | */
71 | public static List cloneList(List targetList) {
72 | return targetList.stream().collect(Collectors.toList());
73 | }
74 |
75 | /**
76 | * Transform a list of objects into an array of primitives
77 | *
78 | * @param listOfObject
79 | * @return
80 | */
81 | public static double[] convertToPrimitives(List listOfObjects) {
82 | return listOfObjects.stream().mapToDouble(Double::doubleValue).toArray();
83 | }
84 |
85 | /**
86 | * Creates a new list with the given elements
87 | * @param values
88 | * @return
89 | */
90 | @SafeVarargs
91 | public static List newList(T... values) {
92 | List arrayList = new ArrayList<>(values.length);
93 | for(T element : values) {
94 | arrayList.add(element);
95 | }
96 |
97 | return arrayList;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/service/RServiceImplIT.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.service;
2 |
3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 |
6 | import java.util.Random;
7 |
8 | import javax.inject.Inject;
9 |
10 | import org.junit.Ignore;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.slf4j.Logger;
14 | import org.slf4j.LoggerFactory;
15 | import org.springframework.boot.test.SpringApplicationConfiguration;
16 | import org.springframework.boot.test.WebIntegrationTest;
17 | import org.springframework.test.context.ActiveProfiles;
18 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
19 |
20 | import com.jfcorugedo.rserver.Application;
21 |
22 | @Ignore("These tests requires R environment installed")
23 | @RunWith(SpringJUnit4ClassRunner.class)
24 | @SpringApplicationConfiguration(classes = Application.class)
25 | @WebIntegrationTest
26 | @ActiveProfiles(profiles={"local", "integrationtest"})
27 | public class RServiceImplIT {
28 |
29 | private static final Logger LOGGER = LoggerFactory.getLogger(RServiceImplIT.class);
30 |
31 | public static final double[] TEST_POPULATION = new double[20];
32 | static{
33 | TEST_POPULATION[0] = 10;
34 | TEST_POPULATION[1] = 5;
35 | TEST_POPULATION[2] = 20;
36 | TEST_POPULATION[3] = 12;
37 | TEST_POPULATION[4] = 4;
38 | TEST_POPULATION[5] = 11;
39 | TEST_POPULATION[6] = 23;
40 | TEST_POPULATION[7] = 0;
41 | TEST_POPULATION[8] = 12;
42 | TEST_POPULATION[9] = 6;
43 | TEST_POPULATION[10] = 6;
44 | TEST_POPULATION[11] = 11;
45 | TEST_POPULATION[12] = 14;
46 | TEST_POPULATION[13] = 13;
47 | TEST_POPULATION[14] = 16;
48 | TEST_POPULATION[15] = 18;
49 | TEST_POPULATION[16] = 19;
50 | TEST_POPULATION[17] = 12;
51 | TEST_POPULATION[18] = 0;
52 | TEST_POPULATION[19] = 4;
53 | }
54 |
55 | public static final double[] TEST_BIG_POPULATION = new double[200];
56 | static{
57 | for(int i = 0 ; i < 10 ; i++) {
58 | for(int j = 0 ; j < 20 ; j++) {
59 | TEST_BIG_POPULATION[i*20+j] = TEST_POPULATION[j];
60 | }
61 | }
62 | }
63 |
64 | @Inject
65 | private RServiceImpl service;
66 |
67 | @Test
68 | public void testBlockFunction() {
69 |
70 | for(int i = 0 ; i < 2 ; i++) {
71 | if(LOGGER.isInfoEnabled()) {
72 | LOGGER.info(Integer.toString(i));
73 | }
74 | service.groupValues(newList(TEST_BIG_POPULATION));
75 | }
76 | }
77 |
78 | @Test
79 | public void testBlockFunctionWithRandom() {
80 |
81 | for(int i = 0 ; i < 2 ; i++) {
82 | if(LOGGER.isInfoEnabled()) {
83 | LOGGER.info(Integer.toString(i));
84 | }
85 | service.groupValues(newList(new Random().doubles(200).map(v -> v*20).toArray()));
86 | }
87 | }
88 |
89 | @Test
90 | public void ksTest() {
91 |
92 | double[] x = new double[]{ 1.0, 3.0, 50.0};
93 | double[] y = new double[]{ 2.0, 4.0, 90.0, 34.1};
94 |
95 | double pValue = service.kolmogorovSmirnovTest(x, y);
96 |
97 | assertThat(pValue).isGreaterThan(0.80);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/generalization/algorithm/ChiSquareTestImplTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.generalization.algorithm;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.Test;
6 |
7 | import com.jfcorugedo.rserver.exception.DimensionMismatchException;
8 |
9 | public class ChiSquareTestImplTest {
10 |
11 | @Test
12 | public void areSimilarDistributionGeneralizable() {
13 |
14 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
15 |
16 | boolean result = chiSquareTest.areGeneralizable(
17 | new long[]{10, 20, 20, 30},
18 | new long[]{100, 200, 300, 400}
19 | );
20 |
21 | assertThat(result).isTrue();
22 | }
23 |
24 | @Test
25 | public void areDifferentDistributionGeneralizable() {
26 |
27 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
28 |
29 | boolean result = chiSquareTest.areGeneralizable(
30 | new long[]{10, 20, 20, 30},
31 | new long[]{200, 100, 300, 400}
32 | );
33 |
34 | assertThat(result).isFalse();
35 | }
36 |
37 | @Test
38 | public void compareTwoValuesWhenOneOfThemHasZeroFrequency() {
39 |
40 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
41 |
42 | boolean result = chiSquareTest.areGeneralizable(
43 | new long[]{0, 2000},
44 | new long[]{0, 10}
45 | );
46 |
47 | assertThat(result).isTrue();
48 | }
49 |
50 | @Test
51 | public void secondSampleHasZeroFrequency() {
52 |
53 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
54 |
55 | boolean result = chiSquareTest.areGeneralizable(
56 | new long[]{10, 0},
57 | new long[]{1, 0}
58 | );
59 |
60 | assertThat(result).isTrue();
61 | }
62 |
63 | @Test
64 | public void firstSampleHasZeroFrequency() {
65 |
66 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
67 |
68 | boolean result = chiSquareTest.areGeneralizable(
69 | new long[]{0, 2000},
70 | new long[]{10, 10}
71 | );
72 |
73 | assertThat(result).isTrue();
74 | }
75 |
76 | @Test
77 | public void binaryFrequenciesWithoutAnyZero() {
78 |
79 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
80 |
81 | boolean result = chiSquareTest.areGeneralizable(
82 | new long[]{1000, 20000},
83 | new long[]{100, 100}
84 | );
85 |
86 | assertThat(result).isFalse();
87 | }
88 |
89 | @Test(expected=DimensionMismatchException.class)
90 | public void compareTwoSamplesWithDifferentLength() {
91 |
92 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
93 |
94 | boolean result = chiSquareTest.areGeneralizable(
95 | new long[]{10, 20, 30},
96 | new long[]{1, 2}
97 | );
98 |
99 | assertThat(result).isTrue();
100 | }
101 |
102 | @Test
103 | public void getAlgorithm() {
104 |
105 | ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
106 |
107 | String algorithm = chiSquareTest.getAlgorithm();
108 |
109 | assertThat(algorithm).isEqualTo("Chi-Square test");
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/sample/rjava/UseREngineInFrontOfJRIEngineIT.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.sample.rjava;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.assertj.core.api.Assertions.fail;
5 |
6 | import org.junit.BeforeClass;
7 | import org.junit.Ignore;
8 | import org.junit.Test;
9 | import org.rosuda.JRI.Rengine;
10 | import org.rosuda.REngine.REXP;
11 | import org.rosuda.REngine.REXPDouble;
12 | import org.rosuda.REngine.REngine;
13 | import org.rosuda.REngine.REngineStdOutput;
14 | import org.rosuda.REngine.RList;
15 | import org.slf4j.Logger;
16 | import org.slf4j.LoggerFactory;
17 |
18 | @Ignore("Maven can't execute this tests because they require a R environmet installed")
19 | public class UseREngineInFrontOfJRIEngineIT {
20 |
21 | private static final Logger LOG = LoggerFactory.getLogger(UseREngineInFrontOfJRIEngineIT.class);
22 |
23 | private static REngine engine;
24 |
25 | @Test
26 | public void testAssignDataFrame() throws Exception{
27 | REXPDouble rIds = new REXPDouble(new double[]{1d,2d});
28 | REXPDouble rValues = new REXPDouble(new double[]{10000d,20000d});
29 |
30 | RList data = new RList();
31 | data.add(rIds);
32 | data.add(rValues);
33 | data.setKeyAt(0, "id");
34 | data.setKeyAt(1, "values");
35 |
36 | engine.assign("data", REXP.createDataFrame(data));
37 |
38 | REXP result = engine.parseAndEval("blockFunction(data,c(\"id\"),c(\"values\"))");
39 |
40 |
41 | System.out.println(result.toDebugString());
42 | }
43 |
44 | @Test
45 | public void testMean() throws Exception{
46 |
47 |
48 | /*
49 | * High-level API - do not use RNI methods unless there is no other way
50 | * to accomplish what you want
51 | */
52 | engine.parseAndEval("rVector=c(1,2,3,4,5)");
53 | REXP result = engine.parseAndEval("meanVal=mean(rVector)");
54 | // generic vectors are RVector to accomodate names
55 | assertThat(result.asDouble()).isEqualTo(3.0);
56 | }
57 |
58 | @Test
59 | public void testSqrt() throws Exception{
60 | REXP result = engine.parseAndEval("sqrt(36)");
61 | assertThat(result.asDouble()).isEqualTo(6.0);
62 | }
63 |
64 | @BeforeClass
65 | public static void setUpR() throws Exception{
66 | // just making sure we have the right version of everything
67 | if (!Rengine.versionCheck()) {
68 | System.err.println("** Version mismatch - Java files don't match library version.");
69 | fail(String.format("Invalid versions. Rengine must have the same version of native library. Rengine version: %d. RNI library version: %d", Rengine.getVersion(), Rengine.rniGetVersion()));
70 | }
71 |
72 | // Enables debug traces
73 | Rengine.DEBUG = 1;
74 |
75 | System.out.println("Creating Rengine (with arguments)");
76 | // 1) we pass the arguments from the command line
77 | // 2) we won't use the main loop at first, we'll start it later
78 | // (that's the "false" as second argument)
79 | // 3) no callback class will be used
80 | engine = REngine.engineForClass("org.rosuda.REngine.JRI.JRIEngine", new String[] { "--no-save" }, new REngineStdOutput(), false);
81 | System.out.println("Rengine created...");
82 |
83 | REXP result = engine.parseAndEval("source(\"/Users/jfcorugedo/Documents/git/RJavaServer/src/test/resources/blockFunction.R\")");
84 | if(result == null) {
85 | LOG.error("blockFunction is not loaded!");
86 | } else {
87 | LOG.info("blockFunction loaded successfully");
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | utf-8
7 | %d [%p] [%t] %c - %m%n
8 |
9 |
10 |
11 |
12 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | true
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/service/RControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.service;
2 |
3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 | import static org.mockito.Matchers.any;
6 | import static org.mockito.Mockito.times;
7 | import static org.mockito.Mockito.verify;
8 | import static org.mockito.Mockito.when;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | import org.junit.Rule;
14 | import org.junit.Test;
15 | import org.mockito.InjectMocks;
16 | import org.mockito.Mock;
17 | import org.mockito.junit.MockitoJUnit;
18 | import org.mockito.junit.MockitoRule;
19 | import org.springframework.http.HttpStatus;
20 | import org.springframework.http.ResponseEntity;
21 |
22 | import com.jfcorugedo.rserver.exception.BadRequestException;
23 | import com.jfcorugedo.rserver.generalization.algorithm.KolmogorovSmirnovTest;
24 |
25 | public class RControllerTest {
26 |
27 | @Rule
28 | public MockitoRule rule = MockitoJUnit.rule();
29 |
30 | @Mock
31 | private RService rService;
32 |
33 | @Mock
34 | private KolmogorovSmirnovTest ksTest;
35 |
36 | @InjectMocks
37 | private RController controller = new RController();
38 |
39 | @Test(expected=BadRequestException.class)
40 | public void testThrowsExceptionWhenNullInput() {
41 |
42 | controller.block(null);
43 | }
44 |
45 | @Test
46 | public void testCallBlock() {
47 |
48 | List values = newList(new double[]{1d,2d});
49 | controller.block(values);
50 |
51 | verify(rService, times(1)).groupValues(values);
52 | }
53 |
54 | @Test(expected=BadRequestException.class)
55 | public void testBlockDiscreteThrowsExceptionWhenNullInput() {
56 |
57 | controller.blockDiscrete(null);
58 | }
59 |
60 | @Test
61 | public void testCallBlockDiscrete() {
62 |
63 | List values = buildList(new String[]{"a", "b", "c"});
64 | controller.blockDiscrete(values);
65 |
66 | verify(rService, times(1)).groupDiscreteValues(values);
67 | }
68 |
69 | @Test
70 | public void testCallSqrt() {
71 |
72 | controller.sqrt(4d);
73 |
74 | verify(rService, times(1)).sqrt(4d);
75 | }
76 |
77 | private List buildList(String[]... strings) {
78 | List result = new ArrayList();
79 | for(String[] value : strings) {
80 | result.add(value);
81 | }
82 | return result;
83 | }
84 |
85 | @Test
86 | public void areGeneralizableCallsKSTest() {
87 |
88 | double[] sampleA = new double[]{1d, 2d};
89 | double[] sampleB = new double[]{3d, 4d};
90 |
91 | controller.areGeneralizable(newList(sampleA, sampleB));
92 |
93 | verify(ksTest, times(1)).areGeneralizable(sampleA, sampleB);
94 | }
95 |
96 | @Test
97 | public void areGeneralizableRetursResult() {
98 |
99 | when(ksTest.areGeneralizable(any(), any())).thenReturn(true);
100 | when(ksTest.getAlgorithm()).thenReturn("K-S test");
101 |
102 | ResponseEntity result = controller.areGeneralizable(newList(new double[]{1d, 2d}, new double[]{1d, 2d}));
103 |
104 | assertThat(result.getBody()).isEqualTo("K-S test: true");
105 | }
106 |
107 | @Test
108 | public void areGeneralizableReturnsBadRequestIfInputDoesNotHaveEnoughValues() {
109 |
110 | ResponseEntity result = controller.areGeneralizable(newList(new double[]{1d, 2d}));
111 |
112 | assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
113 | assertThat(result.getBody()).isEqualTo("This algorithm needs two different list of values");
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/engine/JRIEngineProviderServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 | import static org.mockito.Matchers.anyString;
6 | import static org.mockito.Mockito.mock;
7 | import static org.mockito.Mockito.when;
8 |
9 | import org.junit.Rule;
10 | import org.junit.Test;
11 | import org.mockito.InjectMocks;
12 | import org.mockito.Mock;
13 | import org.mockito.junit.MockitoJUnit;
14 | import org.mockito.junit.MockitoRule;
15 | import org.rosuda.REngine.REXP;
16 | import org.rosuda.REngine.REXPDouble;
17 | import org.rosuda.REngine.REXPInteger;
18 | import org.rosuda.REngine.REXPString;
19 | import org.rosuda.REngine.REngine;
20 |
21 | public class JRIEngineProviderServiceTest {
22 |
23 | @Rule
24 | public MockitoRule rule = MockitoJUnit.rule();
25 |
26 | @Mock
27 | private REngine engine;
28 |
29 | @InjectMocks
30 | private REngineProviderService service = new JRIEngineProviderService();
31 |
32 | @Test(expected=REngineException.class)
33 | public void testThrowsEngineExceptionWhenSomethingGoesWrong() throws Exception {
34 |
35 | when(engine.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong..."));
36 |
37 | service.blockFunction(mock(REXPInteger.class), mock(REXPDouble.class));
38 | }
39 |
40 | @Test
41 | public void testReturnResult() throws Exception {
42 | REXP expectedResult = mock(REXP.class);
43 | when(engine.parseAndEval(anyString())).thenReturn(expectedResult);
44 |
45 | REXP result = service.blockFunction(mock(REXPInteger.class), mock(REXPDouble.class));
46 |
47 | assertThat(result).isNotNull();
48 | assertThat(result).isEqualTo(expectedResult);
49 | }
50 |
51 | @Test(expected=REngineException.class)
52 | public void testThrowsEngineExceptionWhenSomethingGoesWrongInBlockDiscrete() throws Exception {
53 |
54 | when(engine.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong..."));
55 |
56 | service.blockDiscreteFunction(mock(REXPInteger.class), mock(REXPString.class));
57 | }
58 |
59 | @Test
60 | public void testReturnResultBlockDiscreteFunction() throws Exception {
61 | REXP expectedResult = mock(REXP.class);
62 | when(engine.parseAndEval(anyString())).thenReturn(expectedResult);
63 |
64 | REXP result = service.blockDiscreteFunction(mock(REXPInteger.class), mock(REXPString.class));
65 |
66 | assertThat(result).isNotNull();
67 | assertThat(result).isEqualTo(expectedResult);
68 | }
69 |
70 | @Test(expected=REngineException.class)
71 | public void testThrowsEngineExceptionWhenSomethingGoesWrongCallingSqrt() throws Exception {
72 |
73 | when(engine.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong..."));
74 |
75 | service.sqrt(4d);
76 | }
77 |
78 | @Test
79 | public void testReturnResultWhenCallingSqrt() throws Exception {
80 | REXP expectedResult = new REXPDouble(2d);
81 | when(engine.parseAndEval(anyString())).thenReturn(expectedResult);
82 |
83 | double result = service.sqrt(4d);
84 |
85 | assertThat(result).isEqualTo(2d);
86 | }
87 |
88 | @Test(expected=UnsupportedOperationException.class)
89 | public void ksTestIsNotImplementedYet() {
90 |
91 | service.ksTest(mock(REXPDouble.class), mock(REXPDouble.class));
92 | }
93 |
94 | @Test(expected=UnsupportedOperationException.class)
95 | public void blockGeneralFunctionIsNotImplementedYet() {
96 |
97 | service.blockGeneralFunction(mock(REXPInteger.class), newList(), newList());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/blockFunction.r:
--------------------------------------------------------------------------------
1 | library(blockTools)
2 | library(plyr)
3 |
4 | # Block function for continuous variables
5 | blockFunction <- function(data,id.vars,block.vars) {
6 |
7 | # Check if the variance of all block.vars is zero. The block function doesn't work in that case
8 | allVarsAreZero = TRUE
9 | for(blockVar in block.vars) {
10 | if(var(data[[blockVar]]) != 0) allVarsAreZero = FALSE
11 | }
12 |
13 | # If the variance of all block.vars is not zero it's safe to call the block function
14 | if(!allVarsAreZero) {
15 | result <- block(data,
16 | n.tr = 2, # Match pairs, if 3 match triplets, and so on.
17 | id.vars = id.vars,
18 | block.vars = block.vars,
19 | algorithm="optGreedy", # Ok default
20 | distance = "mahalanobis", # OK default
21 | level.two = FALSE,
22 | verbose = FALSE)
23 |
24 | return(result$blocks[[1]])
25 | }
26 | # The variance of all block.vars is zero. That means that all participants have the same value for
27 | # the block vars. We return a combination of pair randomly choosen among the original data
28 | else {
29 | result <- replicate(nrow(data)/2,sample(data[[id.vars]],2)) # Generate pairs
30 | result <- adply(result,2,.id=NULL) # Convert array to data.frame
31 | colnames(result) <- c("Unit 1","Unit 2") # Change column names to "Unit 1", "Unit2"
32 |
33 | return(result)
34 | }
35 |
36 |
37 | }
38 |
39 | # Adds a column to the data.frame with the name value. The value of that column will be 1 if the row has
40 | # that value for the column var and 0 otherwise. For example, if var = city and value = Madrid a new column
41 | # Madrid will be added with 1 if the row has city == Madrid or 0 otherwise.
42 | addColumnForVariable <- function(data,var,value) {
43 |
44 | data[data[[var]] %in% value,value] <- 1
45 | data[!(data[[var]] %in% value),value] <- 0
46 |
47 |
48 | return(data)
49 |
50 | }
51 |
52 | transformDiscreteVariables <- function(data,block.vars) {
53 |
54 | newBlockVars = NULL
55 |
56 | # We might have mutiple block.vars
57 | for(var in block.vars) {
58 | uniqueValues = as.character(unique(data[[var]])) # Get unique values for the blocking variable
59 | uniqueValues = uniqueValues[1:length(uniqueValues)-1] # Get all values except the last one. Block function doesn't work with all values
60 |
61 | newBlockVars = append(uniqueValues,newBlockVars) # Add the values to the blockVars vector
62 |
63 | for(value in uniqueValues) { # For every value, create a column with that value
64 | data <- addColumnForVariable(data,var,value)
65 | }
66 | }
67 |
68 | return(list(data=data,newBlockVars=newBlockVars))
69 |
70 | }
71 |
72 | # Block function for discrete variables. It creates artificial variables for every value of the
73 | # discrete variables and use them as block variables in the block function
74 | blockDiscreteFunction <- function(data,id.vars,block.vars) {
75 |
76 | newBlockVars = NULL
77 | transform <- transformDiscreteVariables(data,block.vars)
78 |
79 | print(transform)
80 |
81 | result <- blockFunction(transform$data,id.vars,transform$newBlockVars) #Do the matching using the new columns
82 |
83 | return(result)
84 | }
85 |
86 | blockGeneralFunction <- function(data,id.vars,discreteVars,continuousVars) {
87 |
88 | transform <- transformDiscreteVariables(data,discreteVars)
89 | print(transform$data)
90 | result <- blockFunction(transform$data,id.vars,c(transform$newBlockVars,continuousVars))
91 |
92 | return(result)
93 |
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/metrics/DatadogReporterConfig.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.metrics;
2 |
3 | import java.util.EnumSet;
4 | import java.util.concurrent.TimeUnit;
5 |
6 | import org.coursera.metrics.datadog.DatadogReporter;
7 | import org.coursera.metrics.datadog.DatadogReporter.Expansion;
8 | import org.coursera.metrics.datadog.transport.HttpTransport;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.boot.context.properties.ConfigurationProperties;
13 | import org.springframework.context.annotation.Bean;
14 | import org.springframework.context.annotation.Configuration;
15 | import org.springframework.context.annotation.Profile;
16 |
17 | import com.codahale.metrics.MetricRegistry;
18 |
19 | /**
20 | * This bean will create and configure a DatadogReporter that will be in charge of sending
21 | * all the metrics collected by Spring Boot actuator system to Datadog.
22 | *
23 | * @see https://www.datadoghq.com/
24 | * @author jfcorugedo
25 | *
26 | */
27 | @Configuration
28 | @ConfigurationProperties("rJavaServer.metrics")
29 | @Profile({ "!local"})
30 | public class DatadogReporterConfig {
31 |
32 | private static final Logger LOGGER = LoggerFactory.getLogger(DatadogReporterConfig.class);
33 |
34 | /** Datadog API key used to authenticate every request to Datadog API */
35 | private String apiKey;
36 |
37 | /** Logical name associated to all the events send by this application */
38 | private String host;
39 |
40 | /** Time, in seconds, between every call to Datadog API. The lower this value the more information will be send to Datadog */
41 | private long period;
42 |
43 | @Bean
44 | @Autowired
45 | public DatadogReporter datadogReporter(MetricRegistry registry) {
46 |
47 | if(LOGGER.isInfoEnabled()) {
48 | LOGGER.info("Initializing Datadog reporter using [ host: {}, period(seconds):{}, api-key:{} ]", getHost(), getPeriod(), getApiKey());
49 | }
50 |
51 | EnumSet expansions = DatadogReporter.Expansion.ALL;
52 | HttpTransport httpTransport = new HttpTransport
53 | .Builder()
54 | .withApiKey(getApiKey())
55 | .build();
56 |
57 | DatadogReporter reporter = DatadogReporter.forRegistry(registry)
58 | .withHost(getHost())
59 | .withTransport(httpTransport)
60 | .withExpansions(expansions)
61 | .build();
62 |
63 | reporter.start(getPeriod(), TimeUnit.SECONDS);
64 |
65 | if(LOGGER.isInfoEnabled()) {
66 | LOGGER.info("Datadog reporter successfully initialized");
67 | }
68 |
69 | return reporter;
70 | }
71 |
72 | /**
73 | * @return Datadog API key used to authenticate every request to Datadog API
74 | */
75 | public String getApiKey() {
76 | return apiKey;
77 | }
78 |
79 | /**
80 | * @param apiKey Datadog API key used to authenticate every request to Datadog API
81 | */
82 | public void setApiKey(String apiKey) {
83 | this.apiKey = apiKey;
84 | }
85 |
86 | /**
87 | * @return Logical name associated to all the events send by this application
88 | */
89 | public String getHost() {
90 | return host;
91 | }
92 |
93 | /**
94 | * @param host Logical name associated to all the events send by this application
95 | */
96 | public void setHost(String host) {
97 | this.host = host;
98 | }
99 |
100 | /**
101 | * @return Time, in seconds, between every call to Datadog API. The lower this value the more information will be send to Datadog
102 | */
103 | public long getPeriod() {
104 | return period;
105 | }
106 |
107 | /**
108 | * @param period Time, in seconds, between every call to Datadog API. The lower this value the more information will be send to Datadog
109 | */
110 | public void setPeriod(long period) {
111 | this.period = period;
112 | }
113 | }
114 |
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RJavaServer [](https://travis-ci.org/jfcorugedo/RJavaServer) [](https://coveralls.io/github/jfcorugedo/RJavaServer?branch=master)
2 | Java server that can call R functions using JRI and rJava packages
3 |
4 | ## Requirements
5 |
6 | This server needs an installation of R in the local OS, as well as the rJava package installed:
7 |
8 | * Install R in your machine (see https://cran.r-project.org/)
9 | * Install rJava using R console: type `install.package("rJava")` inside R console
10 | * Install Rserve using R console: type `install.package("Rserve")` inside R console
11 |
12 | Once these steps are done, you have to configure some variables inside your local system depending on the approach you're going to use.
13 |
14 | ### Rserve:
15 |
16 | * **R_HOME**: Pointing to your local R installation (Ej: /Library/Frameworks/R.framework/Resources)
17 |
18 | ### JRI:
19 |
20 | * **R_HOME**: Pointing to your local R installation (Ej: /Library/Frameworks/R.framework/Resources)
21 | * **LD_LIBRARY_PATH**: Pointing to R lib directory as well as JRI directory (EJ: /app/vendor/R/lib/R/lib:/app/vendor/R/lib/R/bin)
22 |
23 | To sum up, look at the content of the script $R_HOME/library/rJava/jri/run and configure your local system usign the same variable names and values.
24 |
25 | Moreover, the server must be started with the JVM parameter -D-Djava.library.path pointing at the JRI installation directory, ie:
26 |
27 | $JAVA_HOME/bin/java -Dserver.port=$PORT -Djava.library.path=/app/vendor/R/lib/R/library/rJava/jri/ -jar target/RJavaServer.jar
28 |
29 | If you have trouble finding this directory, try to look for the JRI SO library:
30 |
31 | find / -name "libjri.*"
32 |
33 | ## Heroku
34 | In order to execute this server in Heroku, a Procfile has been added to the project. It can be deployen to any Heroku applications that has R environment installed.
35 |
36 | To install R inside a heroku application, you have to use multipack-buildpack and r-buildpack.
37 |
38 | To install this project into a Heroku server, follow theses steps:
39 |
40 | 1. Create a new application that uses multipack buidpack typing in the command line:
41 |
42 | heroku create --stack cedar-14 --buildpack https://github.com/heroku/heroku-buildpack-multi.git --app RJavaServer
43 |
44 | 2. Create a file called `.buildpacks` inside your project, and type here all the Heroku buildpacks you want to install in your environment (**_this project already contains this file_**)
45 |
46 | 3. In addition to your application code, you must tell R which packages must be installed. This task is done inside a file called init.r (**_this project already contains a init.r script_**)
47 |
48 |
49 | ## Usage
50 |
51 | Right now, the REST API has only very basic operations, but you can extend it to call whatever R function you want. Moreover, you can create your own R script and load it using `engine.parseAndEval("source(\"/yourpath/yourscript.r\"")`. (See an example at UseREngineInFrontOfJRIEngineTest)
52 |
53 | To test the server, just try to execute a POST command over the `mean` resource.
54 |
55 | Curl example:
56 |
57 | `curl http://localhost:8080/sqrt -X POST -d "5" -H "Content-type: application/json" -i``
58 |
59 | It should return:
60 |
61 | HTTP/1.1 201 Created
62 | Server: Apache-Coyote/1.1
63 | X-Application-Context: application:local:8080
64 | Content-Type: application/json;charset=UTF-8
65 | Transfer-Encoding: chunked
66 | Date: Sun, 20 Sep 2015 15:46:45 GMT
67 |
68 | 2.23606797749979
69 |
70 |
71 | ## Datadog
72 |
73 | This project has a module that automatically sends metrics to Datadog, an online monitoring system.
74 |
75 | To enable this feature, just create an account in Datadog (https://www.datadoghq.com/) and put your API Key in the configuration file (application.yml):
76 |
77 | metrics:
78 | apiKey:
79 | host:
80 | period:
81 | enabled: true
82 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/common/collection/CollectionUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.common.collection;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.util.ArrayList;
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | import org.junit.Test;
11 |
12 | public class CollectionUtilsTest {
13 |
14 | @Test
15 | public void testCloneList() {
16 | List firstList = new ArrayList();
17 | firstList.add(1);
18 | firstList.add("test");
19 | firstList.add(3.5);
20 |
21 | List secondList = CollectionUtils.cloneList(firstList);
22 |
23 | firstList.add("another");
24 | secondList.remove(1);
25 |
26 | assertThat(firstList).hasSize(4);
27 | assertThat(firstList).containsExactly(1,"test",3.5,"another");
28 | assertThat(secondList).hasSize(2);
29 | assertThat(secondList).containsExactly(1,3.5);
30 | }
31 |
32 |
33 | @Test
34 | public void testConvertToPrimitives() {
35 | List listOfObjects = new ArrayList();
36 | listOfObjects.add(Double.valueOf(1.0));
37 | listOfObjects.add(Double.valueOf(2.0));
38 | listOfObjects.add(Double.valueOf(3.0));
39 |
40 | double[] arrayOfPrimitives = CollectionUtils.convertToPrimitives(listOfObjects);
41 |
42 | assertThat(arrayOfPrimitives).hasSize(3);
43 | assertThat(arrayOfPrimitives).containsExactly(1.0,2.0,3.0);
44 | }
45 |
46 | @Test
47 | public void testArrayToList() {
48 | Double[] array = new Double[]{1d,2d,3d,4d};
49 |
50 | List list = CollectionUtils.arrayToList(array);
51 |
52 | assertThat(list).hasSize(4);
53 | assertThat(list).containsExactly(1d,2d,3d,4d);
54 | }
55 |
56 | @Test
57 | public void testIsEmptyMapReturnsTrueWhenNull() {
58 |
59 | Map nullMap = null;
60 |
61 | boolean result = CollectionUtils.isEmpty(nullMap);
62 |
63 | assertThat(result).isTrue();
64 | assertThat(CollectionUtils.isNotEmpty(nullMap)).isFalse();
65 | }
66 |
67 | @Test
68 | public void testIsEmptyMapReturnsTrueWhenThereIsNoElements() {
69 |
70 | Map emptyMap = new HashMap();
71 |
72 | boolean result = CollectionUtils.isEmpty(emptyMap);
73 |
74 | assertThat(result).isTrue();
75 | assertThat(CollectionUtils.isNotEmpty(emptyMap)).isFalse();
76 | }
77 |
78 | @Test
79 | public void testIsEmptyMapReturnsFalseWhenThereIsAtLeastOneElements() {
80 |
81 | Map notEmptyMap = new HashMap();
82 | notEmptyMap.put(new Object(), null);
83 |
84 | boolean result = CollectionUtils.isEmpty(notEmptyMap);
85 |
86 | assertThat(result).isFalse();
87 | assertThat(CollectionUtils.isNotEmpty(notEmptyMap)).isTrue();
88 | }
89 |
90 | @Test
91 | public void testIsEmptyListReturnsTrueWhenNull() {
92 |
93 | List nullList = null;
94 |
95 | boolean result = CollectionUtils.isEmpty(nullList);
96 |
97 | assertThat(result).isTrue();
98 | assertThat(CollectionUtils.isNotEmpty(nullList)).isFalse();
99 | }
100 |
101 | @Test
102 | public void testIsEmptyListReturnsTrueWhenThereIsNoElements() {
103 |
104 | List emptyList = new ArrayList();
105 |
106 | boolean result = CollectionUtils.isEmpty(emptyList);
107 |
108 | assertThat(result).isTrue();
109 | assertThat(CollectionUtils.isNotEmpty(emptyList)).isFalse();
110 | }
111 |
112 | @Test
113 | public void testIsEmptyListReturnsFalseWhenThereIsAtLeastOneElements() {
114 |
115 | List notEmptyList = new ArrayList();
116 | notEmptyList.add(null);
117 |
118 | boolean result = CollectionUtils.isEmpty(notEmptyList);
119 |
120 | assertThat(result).isFalse();
121 | assertThat(CollectionUtils.isNotEmpty(notEmptyList)).isTrue();
122 | }
123 |
124 | @Test
125 | public void testListToArray() {
126 |
127 | List list = CollectionUtils.newList(1d, 2d, 3d, 4d);
128 |
129 | double[] array = CollectionUtils.listToPrimitiveArray(list);
130 |
131 | assertThat(array).hasSize(list.size());
132 | assertThat(array).containsExactly(1d,2d,3d,4d);
133 | }
134 |
135 | @Test
136 | public void testListToArrayReturnsEmptyArray() {
137 |
138 | List list = null;
139 |
140 | double[] array = CollectionUtils.listToPrimitiveArray(list);
141 |
142 | assertThat(array).hasSize(0);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/engine/JRIEngineProviderServiceIT.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList;
4 |
5 | import java.util.Arrays;
6 | import java.util.stream.Collectors;
7 |
8 | import org.apache.commons.math3.distribution.EnumeratedDistribution;
9 | import org.apache.commons.math3.util.Pair;
10 | import org.junit.BeforeClass;
11 | import org.junit.Ignore;
12 | import org.junit.Test;
13 | import org.rosuda.REngine.REXP;
14 | import org.rosuda.REngine.REXPDouble;
15 | import org.rosuda.REngine.REXPGenericVector;
16 | import org.rosuda.REngine.REXPInteger;
17 | import org.rosuda.REngine.REXPString;
18 | import org.slf4j.Logger;
19 | import org.slf4j.LoggerFactory;
20 |
21 | @Ignore("These tests require R environment installed")
22 | public class JRIEngineProviderServiceIT {
23 |
24 | public static final Logger LOGGER = LoggerFactory.getLogger(JRIEngineProviderServiceIT.class);
25 |
26 | public static final double[] TEST_POPULATION = new double[20];
27 | static{
28 | TEST_POPULATION[0] = 10;
29 | TEST_POPULATION[1] = 5;
30 | TEST_POPULATION[2] = 20;
31 | TEST_POPULATION[3] = 12;
32 | TEST_POPULATION[4] = 4;
33 | TEST_POPULATION[5] = 11;
34 | TEST_POPULATION[6] = 23;
35 | TEST_POPULATION[7] = 0;
36 | TEST_POPULATION[8] = 12;
37 | TEST_POPULATION[9] = 6;
38 | TEST_POPULATION[10] = 6;
39 | TEST_POPULATION[11] = 11;
40 | TEST_POPULATION[12] = 14;
41 | TEST_POPULATION[13] = 13;
42 | TEST_POPULATION[14] = 16;
43 | TEST_POPULATION[15] = 18;
44 | TEST_POPULATION[16] = 19;
45 | TEST_POPULATION[17] = 12;
46 | TEST_POPULATION[18] = 0;
47 | TEST_POPULATION[19] = 4;
48 | }
49 |
50 | public static final double[] TEST_BIG_POPULATION = new double[200];
51 | static{
52 | for(int i = 0 ; i < 10 ; i++) {
53 | for(int j = 0 ; j < 20 ; j++) {
54 | TEST_BIG_POPULATION[i*20+j] = TEST_POPULATION[j];
55 | }
56 | }
57 | }
58 |
59 | private static JRIEngineProviderService providerService;
60 |
61 | @BeforeClass
62 | public static void setUp() {
63 | providerService = new JRIEngineProviderService();
64 | providerService.setBlockFunction("source(\"/Users/jfcorugedo/Documents/git/kmd/kmd-math/blockFunction.R\")");
65 | providerService.setUpR();
66 | }
67 |
68 | @Test
69 | public void testBlockFunction() throws Exception{
70 |
71 | for(int i = 0 ; i < 100 ; i++) {
72 | REXP result = providerService.blockFunction(new REXPInteger(generateIds(200)), new REXPDouble(TEST_BIG_POPULATION));
73 | if(LOGGER.isInfoEnabled()) {
74 | LOGGER.info(blockResultToString(result));
75 | }
76 | }
77 | }
78 |
79 | @Test
80 | @Ignore
81 | public void testBlockDiscreteFunction() {
82 |
83 | REXP result = providerService.blockDiscreteFunction(new REXPInteger(generateIds(200)), new REXPString(generateRandomCities(200)));
84 |
85 | if(LOGGER.isInfoEnabled()) {
86 | LOGGER.info(blockResultToString(result));
87 | }
88 | }
89 |
90 | private String[] generateRandomCities(int sampleSize) {
91 | EnumeratedDistribution cityDistribution =
92 | new EnumeratedDistribution(
93 | newList(
94 | new Pair("Madrid", 1.0),
95 | new Pair("London", 1.0),
96 | new Pair("New York", 1.0),
97 | new Pair("Boston", 1.0),
98 | new Pair("Paris", 1.0),
99 | new Pair("Rome", 1.0),
100 | new Pair("Oslo", 1.0)
101 | ));
102 | return Arrays.stream(cityDistribution.sample(sampleSize)).map(city -> (String)city).collect(Collectors.toList()).toArray(new String[0]);
103 | }
104 |
105 | private int[] generateIds(int length) {
106 | int[] ids = new int[length];
107 | for(int i = 0 ; i < length ; i++) {
108 | ids[i] = i;
109 | }
110 | return ids;
111 | }
112 |
113 | private String blockResultToString(REXP result) {
114 | REXPGenericVector vector = (REXPGenericVector) result;
115 | String[] unit1 = ((REXPString)vector.asList().get(0)).asStrings();
116 | String[] unit2 = ((REXPString)vector.asList().get(1)).asStrings();
117 | double[] distance = ((REXPDouble)vector.asList().get(2)).asDoubles();
118 |
119 | StringBuilder sb = new StringBuilder("\n");
120 |
121 | for(int i = 0 ; i < unit1.length ; i++) {
122 | sb.append(unit1[i]).append(" ").append(unit2[i]).append(" ").append(distance[i]).append("\n");
123 | }
124 |
125 | return sb.toString();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/service/RServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.service;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.List;
6 | import java.util.stream.Collectors;
7 | import java.util.stream.IntStream;
8 |
9 | import javax.inject.Inject;
10 |
11 | import org.rosuda.REngine.REXP;
12 | import org.rosuda.REngine.REXPDouble;
13 | import org.rosuda.REngine.REXPGenericVector;
14 | import org.rosuda.REngine.REXPInteger;
15 | import org.rosuda.REngine.REXPString;
16 | import org.slf4j.Logger;
17 | import org.slf4j.LoggerFactory;
18 | import org.springframework.stereotype.Service;
19 |
20 | import com.jfcorugedo.rserver.engine.REngineProviderService;
21 |
22 | @Service
23 | public class RServiceImpl implements RService{
24 |
25 | private static final Logger LOGGER = LoggerFactory.getLogger(RServiceImpl.class);
26 |
27 | @Inject
28 | private REngineProviderService engineProvider;
29 |
30 | @Override
31 | public List groupValues(List values) {
32 |
33 | REXPInteger rIds = new REXPInteger(generateIds(values.get(0).length));
34 | REXPDouble[] rValues = values.stream().map(rawValues -> new REXPDouble(rawValues)).toArray(REXPDouble[]::new);
35 |
36 | REXP result = engineProvider.blockFunction(rIds, rValues);
37 |
38 | if(LOGGER.isDebugEnabled()) {
39 | LOGGER.debug("blockFunction:\n{}", blockResultToString(result));
40 | }
41 | return formatResult(result);
42 | }
43 |
44 | @Override
45 | public List groupDiscreteValues(List values) {
46 |
47 | REXPInteger rIds = new REXPInteger(generateIds(values.get(0).length));
48 | REXPString[] rValues = values.stream().map(rawValues -> new REXPString(rawValues)).toArray(REXPString[]::new);
49 |
50 | REXP result = engineProvider.blockDiscreteFunction(rIds, rValues);
51 |
52 | if(LOGGER.isDebugEnabled()) {
53 | LOGGER.debug("blockDiscreteFunction:\n{}", blockResultToString(result));
54 | }
55 | return formatResult(result);
56 | }
57 |
58 | @Override
59 | public List groupMultipleValues(List discreteValues, List continuousValues) {
60 |
61 | REXPInteger rIds = new REXPInteger(generateIds(discreteValues.get(0).length));
62 | List discreteNativeValues = discreteValues.stream()
63 | .map(values -> new REXPString(values))
64 | .collect(Collectors.toList());
65 | List continuousNativeValues = continuousValues.stream()
66 | .map(values -> new REXPDouble(values))
67 | .collect(Collectors.toList());
68 |
69 | REXP result = engineProvider.blockGeneralFunction(rIds, discreteNativeValues, continuousNativeValues);
70 |
71 | if(LOGGER.isDebugEnabled()) {
72 | LOGGER.debug("blockGeneralFunction:\n{}", blockResultToString(result));
73 | }
74 | return formatResult(result);
75 | }
76 |
77 | /**
78 | * Format the results from Rserve objects to java standard objects.
79 | * If the results are odd, the last element of the list will contain only one number
80 | * @param result
81 | * @return
82 | */
83 | private List formatResult(REXP result) {
84 | REXPGenericVector vector = (REXPGenericVector) result;
85 |
86 | Integer[] unit1 = getNumbers((REXP)vector.asList().get(0));
87 | Integer[] unit2 = getNumbers((REXP)vector.asList().get(1));
88 |
89 | List resultFormated = new ArrayList<>(unit1.length);
90 | for(int index = 0 ; index < unit2.length -1 ; index++) {
91 | resultFormated.add(new int[]{unit1[index], unit2[index]});
92 | }
93 | if(unit2[unit2.length - 1] == null){
94 | resultFormated.add(new int[]{unit1[unit2.length - 1]});
95 | } else {
96 | resultFormated.add(new int[]{unit1[unit2.length - 1], unit2[unit2.length - 1]});
97 | }
98 |
99 | return resultFormated;
100 | }
101 |
102 | private Integer[] getNumbers(REXP ids) {
103 | if(ids.isString()) {
104 | return Arrays.stream(((REXPString)ids).asStrings())//NOSONAR: This stream doesn't need to be closed
105 | .map(
106 | id -> {
107 | if(id != null) {
108 | return Integer.valueOf(id);
109 | } else {
110 | return null;
111 | }
112 | }
113 | ).toArray(Integer[]::new);
114 | } else {
115 | int[] idsAsArray = ((REXPInteger)ids).asIntegers();
116 | return (Integer[])Arrays.stream(idsAsArray).boxed().toArray(Integer[]::new);//NOSONAR: This stream doesn't need to be closed
117 | }
118 | }
119 |
120 | private int[] generateIds(int length) {
121 | return IntStream.range(0, length).toArray();//NOSONAR: This stream doesn't need to be closed
122 | }
123 |
124 | private String blockResultToString(REXP result) {
125 | REXPGenericVector vector = (REXPGenericVector) result;
126 | String[] unit1 = ((REXPString)vector.asList().get(0)).asStrings();
127 | String[] unit2 = ((REXPString)vector.asList().get(1)).asStrings();
128 | double[] distance = ((REXPDouble)vector.asList().get(2)).asDoubles();
129 |
130 | StringBuilder sb = new StringBuilder("\n");
131 |
132 | for(int i = 0 ; i < unit1.length ; i++) {
133 | sb.append(unit1[i]).append(" ").append(unit2[i]).append(" ").append(distance[i]).append("\n");
134 | }
135 |
136 | return sb.toString();
137 | }
138 |
139 | @Override
140 | public double kolmogorovSmirnovTest(double[] x, double[] y) {
141 |
142 | REXPDouble sampleX = new REXPDouble(x);
143 | REXPDouble sampleY = new REXPDouble(y);
144 |
145 | return engineProvider.ksTest(sampleX, sampleY);
146 | }
147 |
148 | @Override
149 | public double sqrt(double number) {
150 | return engineProvider.sqrt(number);
151 | }
152 |
153 | }
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/engine/JRIEngineProviderService.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import java.util.List;
4 |
5 | import javax.annotation.PostConstruct;
6 |
7 | import org.rosuda.JRI.Rengine;
8 | import org.rosuda.REngine.REXP;
9 | import org.rosuda.REngine.REXPDouble;
10 | import org.rosuda.REngine.REXPInteger;
11 | import org.rosuda.REngine.REXPString;
12 | import org.rosuda.REngine.REngine;
13 | import org.rosuda.REngine.RList;
14 | import org.slf4j.Logger;
15 | import org.slf4j.LoggerFactory;
16 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
17 | import org.springframework.boot.context.properties.ConfigurationProperties;
18 | import org.springframework.stereotype.Service;
19 |
20 | @Service
21 | @ConfigurationProperties("rJavaServer.rengine")
22 | @ConditionalOnProperty(name="rJavaServer.rengine.enableJRI", havingValue="true")
23 | public class JRIEngineProviderService implements REngineProviderService {
24 |
25 | private static final Logger LOGGER = LoggerFactory.getLogger(JRIEngineProviderService.class);
26 |
27 | private REngine engine;
28 |
29 | private String blockFunction;
30 |
31 | /**
32 | * This method initializes REngine properly and make all the operations needed
33 | * to set up the environment.
34 | *
35 | * This JRI implementation must load JRI library and starts JRIEngine
36 | *
37 | */
38 | @PostConstruct
39 | public void setUpR() {//NOSONAR
40 |
41 | try {
42 | //make sure JRI lib can be loaded (it must be present in java.library.path parameter)
43 | //This line is necessary because Rengine.versionCheck() will execute a System.exit if
44 | //it can't load JRI library.
45 | System.loadLibrary("jri");
46 | // just making sure we have the right version of everything
47 | if (!Rengine.versionCheck()) {
48 | LOGGER.error("** Version mismatch - Java files don't match library version.");
49 | LOGGER.error(String.format("Invalid versions. Rengine must have the same version of native library. Rengine version: %d. RNI library version: %d", Rengine.getVersion(), Rengine.rniGetVersion()));
50 | }
51 |
52 | // Enables debug traces
53 | Rengine.DEBUG = 1;
54 |
55 | if(LOGGER.isInfoEnabled()) {
56 | LOGGER.info("Creating Rengine (with arguments)");
57 | }
58 | // 1) we pass the arguments from the command line
59 | // 2) we won't use the main loop at first, we'll start it later
60 | // (that's the "false" as second argument)
61 | // 3) no callback class will be used
62 | this.engine = REngine.engineForClass("org.rosuda.REngine.JRI.JRIEngine", new String[] { "--no-save" }, new REngineStdOutCallback(LOGGER), false);
63 | if(LOGGER.isInfoEnabled()) {
64 | LOGGER.info("Rengine created...");
65 | LOGGER.info("Loading blockFunction from " + getBlockFunction());
66 | }
67 |
68 | REXP result = engine.parseAndEval(getBlockFunction());
69 | if(result == null) {
70 | LOGGER.error("blockFunction is not loaded!");
71 | } else if(LOGGER.isInfoEnabled()) {
72 | LOGGER.info("blockFunction loaded successfully");
73 | }
74 | } catch(Exception|UnsatisfiedLinkError e) {
75 | LOGGER.error("Unexpected error setting up R", e);
76 | }
77 | }
78 |
79 | @Override
80 | public REXP blockDiscreteFunction(REXPInteger ids, REXPString... values) {
81 | RList data = new RList();
82 | data.add(ids);
83 | data.setKeyAt(0, "ids");
84 | StringBuilder valueNames = new StringBuilder();
85 | for(int i = 0 ; i < values.length ; i++) {
86 | data.add(values[i]);
87 | valueNames.append("\"values"+i+"\",");
88 | data.setKeyAt(i+1, "values"+i);
89 | }
90 |
91 | String variableNames = valueNames.substring(0, valueNames.length()-1);
92 | try{
93 | synchronized(engine){
94 | engine.assign("data", REXP.createDataFrame(data));
95 | return engine.parseAndEval("blockDiscreteFunction(data,c(\"ids\"),c(" + variableNames + "))");
96 | }
97 | }catch(Exception e) {
98 | throw new REngineException("Unexpected error while executing blockDiscreteFunction", e);
99 | }
100 | }
101 |
102 | @Override
103 | public double sqrt(double number) {
104 | try {
105 | synchronized(engine) {
106 | return engine.parseAndEval(String.format("sqrt(%f)", number)).asDouble();
107 | }
108 | } catch(Exception e) {
109 | throw new REngineException("Unexpected error while executing sqrt function", e);
110 | }
111 | }
112 |
113 | public String getBlockFunction() {
114 | return blockFunction;
115 | }
116 |
117 | public void setBlockFunction(String blockFunction) {
118 | this.blockFunction = blockFunction;
119 | }
120 |
121 | @Override
122 | public REXP blockFunction(REXPInteger ids, REXPDouble... values) {
123 |
124 | RList data = new RList();
125 | data.add(ids);
126 | data.setKeyAt(0, "ids");
127 | StringBuilder valueNames = new StringBuilder();
128 | for(int i = 0 ; i < values.length ; i++) {
129 | data.add(values[i]);
130 | valueNames.append("\"values"+i+"\",");
131 | data.setKeyAt(i+1, "values"+i);
132 | }
133 |
134 | try{
135 | synchronized(engine){
136 | engine.assign("data", REXP.createDataFrame(data));
137 | return engine.parseAndEval("blockFunction(data,c(\"ids\"),c("+ valueNames.substring(0, valueNames.length()-1) +"))");
138 | }
139 | }catch(Exception e) {
140 | throw new REngineException("Unexpected error while executing blockFunction", e);
141 | }
142 | }
143 |
144 | @Override
145 | public REXP blockGeneralFunction(REXPInteger ids, List discreteValues,
146 | List continuousValues) {
147 | throw new UnsupportedOperationException("JRI engine doesn't implement this method yet");
148 | }
149 |
150 | @Override
151 | public double ksTest(REXPDouble x, REXPDouble y) {
152 |
153 | throw new UnsupportedOperationException("JRI engine doesn't implement this method yet");
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/engine/RServeEngineProviderServiceIT.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 |
6 | import java.util.Arrays;
7 | import java.util.Collections;
8 | import java.util.List;
9 | import java.util.stream.Collectors;
10 |
11 | import javax.inject.Inject;
12 |
13 | import org.apache.commons.math3.distribution.EnumeratedDistribution;
14 | import org.apache.commons.math3.util.Pair;
15 | import org.junit.Ignore;
16 | import org.junit.Test;
17 | import org.junit.runner.RunWith;
18 | import org.rosuda.REngine.REXP;
19 | import org.rosuda.REngine.REXPDouble;
20 | import org.rosuda.REngine.REXPGenericVector;
21 | import org.rosuda.REngine.REXPInteger;
22 | import org.rosuda.REngine.REXPString;
23 | import org.slf4j.Logger;
24 | import org.slf4j.LoggerFactory;
25 | import org.springframework.boot.test.SpringApplicationConfiguration;
26 | import org.springframework.boot.test.WebIntegrationTest;
27 | import org.springframework.test.context.ActiveProfiles;
28 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
29 |
30 | import com.jfcorugedo.rserver.Application;
31 |
32 | /**
33 | * In order to make these test work R_HOME environment variable must be present
34 | *
35 | * Export an environment variable with this value:
36 | *
37 | * R_HOME=/Library/Frameworks/R.framework/Resources
38 | *
39 | * @author jfcorugedo
40 | *
41 | */
42 | @Ignore("These tests require R environment installed and R_HOME variable setted")
43 | @RunWith(SpringJUnit4ClassRunner.class)
44 | @SpringApplicationConfiguration(classes = Application.class)
45 | @WebIntegrationTest
46 | @ActiveProfiles(profiles={"local", "integrationtest"})
47 | public class RServeEngineProviderServiceIT {
48 |
49 | public static final Logger LOGGER = LoggerFactory.getLogger(RServeEngineProviderServiceIT.class);
50 |
51 | public static final double[] TEST_POPULATION = new double[20];
52 | static{
53 | TEST_POPULATION[0] = 10;
54 | TEST_POPULATION[1] = 5;
55 | TEST_POPULATION[2] = 20;
56 | TEST_POPULATION[3] = 12;
57 | TEST_POPULATION[4] = 4;
58 | TEST_POPULATION[5] = 11;
59 | TEST_POPULATION[6] = 23;
60 | TEST_POPULATION[7] = 0;
61 | TEST_POPULATION[8] = 12;
62 | TEST_POPULATION[9] = 6;
63 | TEST_POPULATION[10] = 6;
64 | TEST_POPULATION[11] = 11;
65 | TEST_POPULATION[12] = 14;
66 | TEST_POPULATION[13] = 13;
67 | TEST_POPULATION[14] = 16;
68 | TEST_POPULATION[15] = 18;
69 | TEST_POPULATION[16] = 19;
70 | TEST_POPULATION[17] = 12;
71 | TEST_POPULATION[18] = 0;
72 | TEST_POPULATION[19] = 4;
73 | }
74 |
75 | public static final double[] TEST_BIG_POPULATION = new double[200];
76 | static{
77 | for(int i = 0 ; i < 10 ; i++) {
78 | for(int j = 0 ; j < 20 ; j++) {
79 | TEST_BIG_POPULATION[i*20+j] = TEST_POPULATION[j];
80 | }
81 | }
82 | }
83 |
84 |
85 | @Inject
86 | private RServeEngineProviderService providerService;
87 |
88 | @Test
89 | public void testBlockFunction() throws Exception{
90 |
91 | List bigPopulation = Arrays.stream(TEST_BIG_POPULATION).boxed().collect(Collectors.toList());
92 | Collections.shuffle(bigPopulation);
93 | double[] shuffledPopulation = bigPopulation.stream().mapToDouble(Double::doubleValue).toArray();
94 | REXP result = providerService.blockFunction(new REXPInteger(generateIds(200)), new REXPDouble(TEST_BIG_POPULATION), new REXPDouble(shuffledPopulation));
95 |
96 | if(LOGGER.isInfoEnabled()) {
97 | LOGGER.info(blockResultToString(result));
98 | }
99 |
100 | assertThat((((REXPString)((REXPGenericVector)result).asList().get(0)).asStrings())).hasSize(100);
101 | }
102 |
103 | @Test
104 | public void testGenericBlockFunction() {
105 |
106 | List bigPopulation = Arrays.stream(TEST_BIG_POPULATION).boxed().collect(Collectors.toList());
107 | Collections.shuffle(bigPopulation);
108 | double[] shuffledPopulation = bigPopulation.stream().mapToDouble(Double::doubleValue).toArray();
109 | REXP result = providerService.blockGeneralFunction(new REXPInteger(generateIds(200)), newList(new REXPString(generateRandomCities(200))), newList(new REXPDouble(shuffledPopulation)));
110 |
111 | if(LOGGER.isInfoEnabled()) {
112 | LOGGER.info(blockResultToString(result));
113 | }
114 |
115 | assertThat((((REXPString)((REXPGenericVector)result).asList().get(0)).asStrings())).hasSize(100);
116 | }
117 |
118 | @Test
119 | public void testBlockDiscreteFunction() {
120 |
121 | REXP result = providerService.blockDiscreteFunction(new REXPInteger(generateIds(50)), new REXPString(generateRandomCities(50)));
122 |
123 | if(LOGGER.isInfoEnabled()) {
124 | LOGGER.info(blockResultToString(result));
125 | }
126 |
127 | assertThat((((REXPString)((REXPGenericVector)result).asList().get(0)).asStrings())).hasSize(25);
128 | }
129 |
130 | @Test
131 | public void testBlockDiscreteFunctionUsingTwoVariables() {
132 |
133 | REXP result = providerService.blockDiscreteFunction(new REXPInteger(generateIds(50)), new REXPString(generateRandomCities(50)), new REXPString(generateRandomCountries(50)));
134 |
135 | if(LOGGER.isInfoEnabled()) {
136 | LOGGER.info(blockResultToString(result));
137 | }
138 |
139 | assertThat((((REXPString)((REXPGenericVector)result).asList().get(0)).asStrings())).hasSize(25);
140 | }
141 |
142 | private String[] generateRandomCities(int sampleSize) {
143 | EnumeratedDistribution cityDistribution =
144 | new EnumeratedDistribution(
145 | newList(
146 | new Pair("Madrid", 1.0),
147 | new Pair("London", 1.0),
148 | new Pair("New York", 1.0),
149 | new Pair("Boston", 1.0),
150 | new Pair("Paris", 1.0),
151 | new Pair("Rome", 1.0),
152 | new Pair("Oslo", 1.0)
153 | ));
154 | return Arrays.stream(cityDistribution.sample(sampleSize)).map(city -> (String)city).collect(Collectors.toList()).toArray(new String[0]);
155 | }
156 |
157 | private String[] generateRandomCountries(int sampleSize) {
158 | EnumeratedDistribution cityDistribution =
159 | new EnumeratedDistribution(
160 | newList(
161 | new Pair("Spain", 1.0),
162 | new Pair("England", 1.0),
163 | new Pair("USA", 1.0),
164 | new Pair("France", 1.0),
165 | new Pair("Italy", 1.0),
166 | new Pair("Norway", 1.0),
167 | new Pair("Germany", 1.0)
168 | ));
169 | return Arrays.stream(cityDistribution.sample(sampleSize)).map(city -> (String)city).collect(Collectors.toList()).toArray(new String[0]);
170 | }
171 |
172 | private int[] generateIds(int length) {
173 | int[] ids = new int[length];
174 | for(int i = 0 ; i < length ; i++) {
175 | ids[i] = i;
176 | }
177 | return ids;
178 | }
179 |
180 | private String blockResultToString(REXP result) {
181 | REXPGenericVector vector = (REXPGenericVector) result;
182 | String[] unit1 = ((REXPString)vector.asList().get(0)).asStrings();
183 | String[] unit2 = ((REXPString)vector.asList().get(1)).asStrings();
184 | double[] distance = ((REXPDouble)vector.asList().get(2)).asDoubles();
185 |
186 | StringBuilder sb = new StringBuilder("\n");
187 |
188 | for(int i = 0 ; i < unit1.length ; i++) {
189 | sb.append(unit1[i]).append(" ").append(unit2[i]).append(" ").append(distance[i]).append("\n");
190 | }
191 |
192 | return sb.toString();
193 | }
194 |
195 | @Test
196 | public void ksTest() {
197 |
198 | REXPDouble x = new REXPDouble(new double[]{3.5,4.0,5.0,25.0});
199 | REXPDouble y = new REXPDouble(new double[]{3.5,4.0,5.0,25.0});
200 |
201 | double pValue = providerService.ksTest(x, y);
202 |
203 | assertThat(pValue).isGreaterThan(0.05);
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/src/main/java/com/jfcorugedo/rserver/engine/RServeEngineProviderService.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import static java.lang.String.format;
4 |
5 | import java.util.List;
6 |
7 | import javax.annotation.PostConstruct;
8 | import javax.annotation.PreDestroy;
9 | import javax.inject.Inject;
10 |
11 | import org.rosuda.REngine.REXP;
12 | import org.rosuda.REngine.REXPDouble;
13 | import org.rosuda.REngine.REXPInteger;
14 | import org.rosuda.REngine.REXPString;
15 | import org.rosuda.REngine.REngine;
16 | import org.rosuda.REngine.RList;
17 | import org.rosuda.REngine.Rserve.RConnection;
18 | import org.slf4j.Logger;
19 | import org.slf4j.LoggerFactory;
20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
21 | import org.springframework.boot.context.properties.ConfigurationProperties;
22 | import org.springframework.stereotype.Service;
23 |
24 | @Service
25 | @ConfigurationProperties("rJavaServer.rengine")
26 | @ConditionalOnProperty(name="rJavaServer.rengine.enableJRI", havingValue="false", matchIfMissing=true)
27 | public class RServeEngineProviderService implements REngineProviderService {
28 |
29 | private static final Logger LOGGER = LoggerFactory.getLogger(RServeEngineProviderService.class);
30 |
31 | /** Code returned by R script when everything goes ok */
32 | private static final int SUCCESS_CODE = 0;
33 |
34 | /** Full path to the rserve configuration file */
35 | private String rserveConf;
36 |
37 | /** Full path to the R executable file */
38 | private String rexe;
39 |
40 | @Inject
41 | private RConnectionFactory rConnectionFactory;
42 |
43 | /**
44 | * This method initializes REngine properly and make all the operations needed
45 | * to set up the environment.
46 | *
47 | * This RServe implementation must run R in a separate process and check the connection.
48 | *
49 | */
50 | @PostConstruct
51 | public void setUpR() {//NOSONAR
52 |
53 | REngine engine = null;
54 | try {
55 |
56 | shutdownPreviousRuningInstances();
57 |
58 | String command = String.format("echo 'library(Rserve);Rserve(FALSE,args=\"--no-save --slave --RS-conf %s\")'|%s --no-save --slave", rserveConf, rexe);
59 |
60 | if(LOGGER.isInfoEnabled()) {
61 | LOGGER.info("Starting RServe process using this command:\n{}", command);
62 | }
63 | ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c", command);
64 | builder.inheritIO();
65 | Process rProcess = builder.start();
66 |
67 | if(LOGGER.isInfoEnabled()) {
68 | LOGGER.info("Waiting for Rserve to start...");
69 | }
70 | int execCodeResult = rProcess.waitFor();
71 |
72 | if(execCodeResult != SUCCESS_CODE) {
73 | LOGGER.error(String.format("Unexpected error code starting RServe: %d", execCodeResult));
74 | } else {
75 | LOGGER.error("RServe started successfully");
76 | }
77 |
78 | if(LOGGER.isInfoEnabled()) {
79 | LOGGER.info("Opening connection to RServe daemon....");
80 | }
81 | engine = rConnectionFactory.getConnection();
82 | if(LOGGER.isInfoEnabled()) {
83 | LOGGER.info(String.format("Obtaining R server version: %d", ((RConnection)engine).getServerVersion()));
84 | }
85 |
86 | } catch(Exception e) {
87 | LOGGER.error("Unexpected error setting up RServe environment", e);
88 | } finally {
89 | rConnectionFactory.releaseConnection(engine);
90 | }
91 | }
92 |
93 | protected void shutdownPreviousRuningInstances() {
94 | if(LOGGER.isInfoEnabled()) {
95 | LOGGER.info("Cleaning old Rserve running instances...");
96 | }
97 |
98 | tearDown();
99 | }
100 |
101 | @PreDestroy
102 | public void tearDown() {
103 | try {
104 | if(LOGGER.isInfoEnabled()) {
105 | LOGGER.info("Shuting down Rserve daemon....");
106 | }
107 | RConnection rConnection = rConnectionFactory.getConnection();
108 | rConnection.shutdown();
109 | rConnectionFactory.releaseConnection(rConnection);
110 | if(LOGGER.isInfoEnabled()) {
111 | LOGGER.info("Shutdown signal sent to Rserve daemon");
112 | }
113 | } catch(Exception e) {
114 | LOGGER.error("Unexpected error shuting down RServe", e);
115 | }
116 | }
117 |
118 | @Override
119 | public REXP blockFunction(REXPInteger ids, REXPDouble... values) {
120 |
121 | RList data = new RList();
122 | data.add(ids);
123 | data.setKeyAt(0, "ids");
124 | StringBuilder valueNames = new StringBuilder();
125 | for(int i = 0 ; i < values.length ; i++) {
126 | data.add(values[i]);
127 | valueNames.append(format("\"values%d\",", i));
128 | data.setKeyAt(i+1, format("values%d",i));
129 | }
130 |
131 | String variableNames = valueNames.substring(0, valueNames.length()-1);
132 | REngine engine = null;
133 | try {
134 | engine = rConnectionFactory.getConnection();
135 | engine.assign("data", REXP.createDataFrame(data));
136 | return engine.parseAndEval(format("blockFunction(data,c(\"ids\"),c(%s))", variableNames));
137 | }catch(Exception e) {
138 | throw new REngineException("Unexpected error while executing blockFunction", e);
139 | } finally {
140 | rConnectionFactory.releaseConnection(engine);
141 | }
142 | }
143 |
144 | @Override
145 | public REXP blockGeneralFunction(REXPInteger ids, List discreteValues, List continuousValues) {
146 |
147 | RList data = new RList();
148 | data.add(ids);
149 | data.setKeyAt(0, "ids");
150 | StringBuilder discreteVariables = new StringBuilder();
151 | for(int i = 0 ; i < discreteValues.size() ; i++) {
152 | data.add(discreteValues.get(i));
153 | discreteVariables.append(format("\"discreteValues%d\",", i));
154 | data.setKeyAt(i+1, format("discreteValues%d", i));
155 | }
156 |
157 | StringBuilder continuousVariables = new StringBuilder();
158 | for(int i = 0 ; i < continuousValues.size() ; i++) {
159 | data.add(continuousValues.get(i));
160 | continuousVariables.append(format("\"continuousValues%d\",", i));
161 | data.setKeyAt(i + discreteValues.size() + 1, format("continuousValues%d", i));
162 | }
163 |
164 | String discreteVariableNames = discreteVariables.substring(0, discreteVariables.length()-1);
165 | String continuousVariableNames = continuousVariables.substring(0, continuousVariables.length()-1);
166 | REngine engine = null;
167 | try {
168 | engine = rConnectionFactory.getConnection();
169 | engine.assign("data", REXP.createDataFrame(data));
170 | return engine.parseAndEval(format("blockGeneralFunction(data,c(\"ids\"),c(%s),c(%s))", discreteVariableNames, continuousVariableNames));
171 | }catch(Exception e) {
172 | throw new REngineException("Unexpected error while executing blockFunction", e);
173 | } finally {
174 | rConnectionFactory.releaseConnection(engine);
175 | }
176 | }
177 |
178 | @Override
179 | public REXP blockDiscreteFunction(REXPInteger ids, REXPString... values) {
180 | RList data = new RList();
181 | data.add(ids);
182 | data.setKeyAt(0, "ids");
183 | StringBuilder valueNames = new StringBuilder();
184 | for(int i = 0 ; i < values.length ; i++) {
185 | data.add(values[i]);
186 | valueNames.append(format("\"values%d\",", i));
187 | data.setKeyAt(i+1, format("values%d",i));
188 | }
189 |
190 | REngine engine = null;
191 | String variableNames = valueNames.substring(0, valueNames.length()-1);
192 | try {
193 | engine = rConnectionFactory.getConnection();
194 | engine.assign("data", REXP.createDataFrame(data));
195 | return engine.parseAndEval(format("blockDiscreteFunction(data,c(\"ids\"),c(%s))", variableNames));
196 | }catch(Exception e) {
197 | throw new REngineException("Unexpected error while executing blockDiscreteFunction", e);
198 | } finally {
199 | rConnectionFactory.releaseConnection(engine);
200 | }
201 | }
202 |
203 | @Override
204 | public double sqrt(double number) {
205 | REngine engine = null;
206 | try {
207 | engine = rConnectionFactory.getConnection();
208 | return engine.parseAndEval(String.format("sqrt(%f)", number)).asDouble();
209 | } catch(Exception e) {
210 | throw new REngineException("Unexpected error while executing sqrt function", e);
211 | } finally {
212 | rConnectionFactory.releaseConnection(engine);
213 | }
214 | }
215 |
216 | public String getRserveConf() {
217 | return rserveConf;
218 | }
219 |
220 | public void setRserveConf(String rserveConf) {
221 | this.rserveConf = rserveConf;
222 | }
223 |
224 | public String getRexe() {
225 | return rexe;
226 | }
227 |
228 | public void setRexe(String rexe) {
229 | this.rexe = rexe;
230 | }
231 |
232 | @Override
233 | public double ksTest(REXPDouble x, REXPDouble y) {
234 |
235 | REngine engine = null;
236 | try {
237 | engine = rConnectionFactory.getConnection();
238 | engine.assign("x", x);
239 | engine.assign("y", y);
240 | return engine.parseAndEval("ks.test(x, y)['p.value']").asList().at(0).asDouble();
241 | }catch(Exception e) {
242 | throw new REngineException("Unexpected error while executing Kolmogorov-Smirnov test", e);
243 | } finally {
244 | rConnectionFactory.releaseConnection(engine);
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.jfcorugedo.rserver
5 | rjavaserver
6 | 0.0.1-SNAPSHOT
7 |
8 |
9 | org.springframework.boot
10 | spring-boot-starter-parent
11 | 1.2.7.RELEASE
12 |
13 |
14 |
15 |
16 | UTF-8
17 | UTF-8
18 | 1.8
19 | com.jfcorugedo.rserver.Application
20 |
21 |
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-web
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-actuator
30 |
31 |
32 | org.rosuda.REngine
33 | REngine
34 | 2.1.0
35 |
36 |
37 | com.github.lucarosellini.rJava
38 | JRI
39 | 0.9-7
40 |
41 |
42 | com.github.lucarosellini.rJava
43 | JRIEngine
44 | 0.9-7
45 |
46 |
47 | org.rosuda.REngine
48 | Rserve
49 | 1.8.1
50 |
51 |
52 | javax.inject
53 | javax.inject
54 | 1
55 |
56 |
57 |
58 |
59 | org.springframework.boot
60 | spring-boot-starter-aop
61 |
62 |
63 | io.dropwizard.metrics
64 | metrics-core
65 |
66 |
67 | com.ryantenney.metrics
68 | metrics-spring
69 | 3.1.2
70 |
71 |
72 |
73 |
74 |
75 | org.coursera
76 | dropwizard-metrics-datadog
77 | 1.1.3
78 |
79 |
80 | org.eclipse.jetty
81 | jetty-server
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | org.springframework.boot
90 | spring-boot-starter-test
91 | test
92 |
93 |
94 | org.assertj
95 | assertj-core
96 | 3.0.0
97 | test
98 |
99 |
100 | junit
101 | junit
102 | test
103 |
104 |
105 | org.mockito
106 | mockito-core
107 | test
108 |
109 |
110 | org.apache.commons
111 | commons-math3
112 | 3.5
113 |
114 |
115 | org.springframework.boot
116 | spring-boot-configuration-processor
117 | true
118 |
119 |
120 | org.apache.commons
121 | commons-lang3
122 | 3.3.2
123 |
124 |
125 |
126 |
127 | rjavaserver
128 |
129 |
130 | org.springframework.boot
131 | spring-boot-maven-plugin
132 |
133 |
134 | maven-failsafe-plugin
135 |
136 |
137 |
138 | integration-test
139 | verify
140 |
141 |
142 | true
143 |
144 | **/*IT.java
145 |
146 |
147 | **/Application.java
148 |
149 | ${jacoco.it.argLine}
150 | UTF-8
151 | alphabetical
152 |
153 |
154 |
155 |
156 |
157 | maven-surefire-plugin
158 |
159 | true
160 |
161 | **/*Test.java
162 |
163 |
164 | **/*IT.java
165 | **/Application.java
166 |
167 |
168 | ${jacoco.argLine}
169 |
170 |
171 | alphabetical
172 |
173 |
174 |
175 | org.apache.maven.surefire
176 | surefire-junit47
177 | 2.15
178 |
179 |
180 |
181 |
182 | org.apache.maven.plugins
183 | maven-enforcer-plugin
184 | 1.3.1
185 |
186 |
187 | enforce-versions
188 |
189 | enforce
190 |
191 |
192 |
193 |
194 |
195 |
196 | You are running an older version of Maven. This
197 | application requires at least Maven 3.0
198 | [3.0.0,)
199 |
200 |
201 | You are running an older version of Java. This
202 | application requires at least JDK ${java.version}
203 | [${java.version},)
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | org.jacoco
212 | jacoco-maven-plugin
213 | 0.7.6.201602180812
214 |
215 |
216 | prepare-agent
217 |
218 | prepare-agent
219 |
220 |
221 |
222 | **/*Test
223 |
224 |
225 | com.jfcorugedo.*
226 |
227 | jacoco.argLine
228 |
229 |
230 |
231 |
232 | report
233 | prepare-package
234 |
235 | report
236 |
237 |
238 |
239 |
240 | prepare-agent-it
241 | pre-integration-test
242 |
243 | prepare-agent
244 |
245 |
246 | ${project.build.directory}/jacoco-integration.exec
247 |
248 | **/*IT
249 |
250 |
251 | com.jfcorugedo.*
252 |
253 | jacoco.it.argLine
254 |
255 |
256 |
257 |
258 | report-it
259 | post-integration-test
260 |
261 | report
262 |
263 |
264 | ${project.reporting.outputDirectory}/jacoco-it
265 | ${project.build.directory}/jacoco-integration.exec
266 |
267 |
268 |
269 |
270 |
271 |
272 | org.eluder.coveralls
273 | coveralls-maven-plugin
274 | 4.1.0
275 |
276 |
277 |
278 |
279 |
280 | org.sonarsource.scanner.maven
281 | sonar-maven-plugin
282 | 3.0.1
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 | sonar
291 |
292 | true
293 |
294 |
295 |
296 | http://my-sonar.herokuapp.com/
297 | admin
298 | admin.my-sonar
299 | java
300 | true
301 | target/jacoco-integration.exec
302 | 1.8
303 |
304 |
305 |
306 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/engine/RServeEngineProviderServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.engine;
2 |
3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 | import static org.mockito.Matchers.any;
6 | import static org.mockito.Matchers.anyString;
7 | import static org.mockito.Matchers.eq;
8 | import static org.mockito.Mockito.mock;
9 | import static org.mockito.Mockito.times;
10 | import static org.mockito.Mockito.verify;
11 | import static org.mockito.Mockito.when;
12 |
13 | import java.util.List;
14 |
15 | import org.junit.Rule;
16 | import org.junit.Test;
17 | import org.mockito.ArgumentCaptor;
18 | import org.mockito.InjectMocks;
19 | import org.mockito.Mock;
20 | import org.mockito.junit.MockitoJUnit;
21 | import org.mockito.junit.MockitoRule;
22 | import org.rosuda.REngine.REXP;
23 | import org.rosuda.REngine.REXPDouble;
24 | import org.rosuda.REngine.REXPGenericVector;
25 | import org.rosuda.REngine.REXPInteger;
26 | import org.rosuda.REngine.REXPString;
27 | import org.rosuda.REngine.RList;
28 | import org.rosuda.REngine.Rserve.RConnection;
29 |
30 | public class RServeEngineProviderServiceTest {
31 |
32 | @Rule
33 | public MockitoRule rule = MockitoJUnit.rule();
34 |
35 | @Mock
36 | private RConnectionFactory rConnectionFactory;
37 |
38 | @InjectMocks
39 | private RServeEngineProviderService rServeEngineProviderService;
40 |
41 | @Test
42 | public void blockGeneralFunction() throws Exception{
43 | RConnection rConnectionMock = mock(RConnection.class);
44 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
45 |
46 | rServeEngineProviderService.blockGeneralFunction(getDemoIds(), getDemoDiscreteVars(), getDemoContinuousVars());
47 |
48 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class));
49 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class);
50 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture());
51 | assertThat(rCommand.getValue()).isEqualTo("blockGeneralFunction(data,c(\"ids\"),c(\"discreteValues0\"),c(\"continuousValues0\"))");
52 | }
53 |
54 | private List getDemoDiscreteVars() {
55 |
56 | return newList(new REXPString(new String[]{"A", "B", "B", "A"}));
57 | }
58 |
59 | private List getDemoContinuousVars() {
60 |
61 | return newList(new REXPDouble(new double[]{1.0, 2.0, 3.0, 4.0}));
62 | }
63 |
64 | private REXPInteger getDemoIds() {
65 |
66 | return new REXPInteger(new int[]{0,1,2,3});
67 | }
68 |
69 | @Test(expected=REngineException.class)
70 | public void blockGeneralFunctionUnexpectedError() throws Exception{
71 |
72 | RConnection rConnectionMock = mock(RConnection.class);
73 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong..."));
74 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
75 |
76 | rServeEngineProviderService.blockGeneralFunction(getDemoIds(), getDemoDiscreteVars(), getDemoContinuousVars());
77 |
78 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class));
79 | verify(rConnectionMock, times(1)).parseAndEval(anyString());
80 | }
81 |
82 | @Test
83 | public void blockFunction() throws Exception{
84 | RConnection rConnectionMock = mock(RConnection.class);
85 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
86 |
87 | rServeEngineProviderService.blockFunction(getDemoIds(), getDemoContinuousVars().get(0));
88 |
89 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class));
90 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class);
91 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture());
92 | assertThat(rCommand.getValue()).isEqualTo("blockFunction(data,c(\"ids\"),c(\"values0\"))");
93 | }
94 |
95 | @Test
96 | public void blockFunctionUsingMoreThanOneVariable() throws Exception{
97 | RConnection rConnectionMock = mock(RConnection.class);
98 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
99 |
100 | rServeEngineProviderService.blockFunction(getDemoIds(), getDemoContinuousVars().get(0), getDemoContinuousVars().get(0));
101 |
102 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class));
103 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class);
104 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture());
105 | assertThat(rCommand.getValue()).isEqualTo("blockFunction(data,c(\"ids\"),c(\"values0\",\"values1\"))");
106 | }
107 |
108 | @Test(expected=REngineException.class)
109 | public void blockFunctionUnexpectedError() throws Exception{
110 |
111 | RConnection rConnectionMock = mock(RConnection.class);
112 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong..."));
113 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
114 |
115 | rServeEngineProviderService.blockFunction(getDemoIds(), getDemoContinuousVars().get(0));
116 |
117 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class));
118 | verify(rConnectionMock, times(1)).parseAndEval(anyString());
119 | }
120 |
121 | @Test
122 | public void blockDiscreteFunction() throws Exception{
123 | RConnection rConnectionMock = mock(RConnection.class);
124 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
125 |
126 | rServeEngineProviderService.blockDiscreteFunction(getDemoIds(), getDemoDiscreteVars().get(0));
127 |
128 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class));
129 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class);
130 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture());
131 | assertThat(rCommand.getValue()).isEqualTo("blockDiscreteFunction(data,c(\"ids\"),c(\"values0\"))");
132 | }
133 |
134 | @Test
135 | public void blockDiscreteFunctionUsingMoreThanOneVariable() throws Exception{
136 | RConnection rConnectionMock = mock(RConnection.class);
137 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
138 |
139 | rServeEngineProviderService.blockDiscreteFunction(getDemoIds(), getDemoDiscreteVars().get(0), getDemoDiscreteVars().get(0));
140 |
141 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class));
142 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class);
143 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture());
144 | assertThat(rCommand.getValue()).isEqualTo("blockDiscreteFunction(data,c(\"ids\"),c(\"values0\",\"values1\"))");
145 | }
146 |
147 | @Test(expected=REngineException.class)
148 | public void blockDiscreteFunctionUnexpectedError() throws Exception{
149 |
150 | RConnection rConnectionMock = mock(RConnection.class);
151 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException("Something goes wrong..."));
152 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
153 |
154 | rServeEngineProviderService.blockDiscreteFunction(getDemoIds(), getDemoDiscreteVars().get(0));
155 |
156 | verify(rConnectionMock, times(1)).assign(eq("data"), any(REXP.class));
157 | verify(rConnectionMock, times(1)).parseAndEval(anyString());
158 | }
159 |
160 | @Test
161 | public void ksTest() throws Exception{
162 |
163 | RConnection rConnectionMock = mock(RConnection.class);
164 | when(rConnectionMock.parseAndEval(anyString())).thenReturn(new REXPGenericVector(new RList(new REXP[]{new REXPDouble(new double[]{0.15})})));
165 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
166 | REXPDouble demoVars = getDemoContinuousVars().get(0);
167 |
168 | rServeEngineProviderService.ksTest(demoVars, demoVars);
169 |
170 | verify(rConnectionMock, times(1)).assign(eq("x"), any(REXP.class));
171 | verify(rConnectionMock, times(1)).assign(eq("y"), any(REXP.class));
172 | ArgumentCaptor rCommand = ArgumentCaptor.forClass(String.class);
173 | verify(rConnectionMock, times(1)).parseAndEval(rCommand.capture());
174 | assertThat(rCommand.getValue()).isEqualTo("ks.test(x, y)['p.value']");
175 | }
176 |
177 | @Test(expected=REngineException.class)
178 | public void ksTestThrowsException() throws Exception{
179 |
180 | RConnection rConnectionMock = mock(RConnection.class);
181 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException());
182 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
183 | REXPDouble demoVars = getDemoContinuousVars().get(0);
184 |
185 | rServeEngineProviderService.ksTest(demoVars, demoVars);
186 | }
187 |
188 | @Test
189 | public void testSqrt() throws Exception{
190 |
191 | RConnection rConnectionMock = mock(RConnection.class);
192 | when(rConnectionMock.parseAndEval("sqrt(4.000000)")).thenReturn(new REXPDouble(2.0));
193 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
194 |
195 | double result = rServeEngineProviderService.sqrt(4.0);
196 |
197 | assertThat(result).isEqualTo(2.0);
198 | }
199 |
200 | @Test(expected=REngineException.class)
201 | public void testSqrtThrowsException() throws Exception{
202 |
203 | RConnection rConnectionMock = mock(RConnection.class);
204 | when(rConnectionMock.parseAndEval(anyString())).thenThrow(new RuntimeException());
205 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
206 |
207 | rServeEngineProviderService.sqrt(4.0);
208 | }
209 |
210 | @Test
211 | public void shutdownPreviousRuningInstancesTriesToShutdownAndCloseConnection() throws Exception{
212 |
213 | RConnection rConnectionMock = mock(RConnection.class);
214 | when(rConnectionFactory.getConnection()).thenReturn(rConnectionMock);
215 |
216 | rServeEngineProviderService.shutdownPreviousRuningInstances();
217 |
218 | verify(rConnectionMock, times(1)).shutdown();
219 | verify(rConnectionFactory, times(1)).releaseConnection(rConnectionMock);
220 | }
221 |
222 | @Test
223 | public void shutdownPreviousRuningInstancesDoesNotPropagateAnyException() throws Exception{
224 |
225 | when(rConnectionFactory.getConnection()).thenThrow(new RuntimeException("Test expception"));
226 |
227 | rServeEngineProviderService.shutdownPreviousRuningInstances();
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/test/java/com/jfcorugedo/rserver/service/RServiceImplTest.java:
--------------------------------------------------------------------------------
1 | package com.jfcorugedo.rserver.service;
2 |
3 | import static com.jfcorugedo.rserver.common.collection.CollectionUtils.newList;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 | import static org.mockito.Matchers.any;
6 | import static org.mockito.Matchers.anyListOf;
7 | import static org.mockito.Matchers.anyVararg;
8 | import static org.mockito.Mockito.times;
9 | import static org.mockito.Mockito.verify;
10 | import static org.mockito.Mockito.when;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | import org.junit.Rule;
16 | import org.junit.Test;
17 | import org.mockito.ArgumentCaptor;
18 | import org.mockito.InjectMocks;
19 | import org.mockito.Mock;
20 | import org.mockito.junit.MockitoJUnit;
21 | import org.mockito.junit.MockitoRule;
22 | import org.rosuda.REngine.REXP;
23 | import org.rosuda.REngine.REXPDouble;
24 | import org.rosuda.REngine.REXPGenericVector;
25 | import org.rosuda.REngine.REXPInteger;
26 | import org.rosuda.REngine.REXPString;
27 | import org.rosuda.REngine.RList;
28 |
29 | import com.jfcorugedo.rserver.engine.REngineProviderService;
30 |
31 | public class RServiceImplTest {
32 |
33 | @Rule
34 | public MockitoRule rule = MockitoJUnit.rule();
35 |
36 | @Mock
37 | private REngineProviderService engineProvider;
38 |
39 | @InjectMocks
40 | private RServiceImpl service = new RServiceImpl();
41 |
42 | @Test
43 | public void testBlockFunction() throws Exception{
44 |
45 | when(engineProvider.blockFunction(any(REXPInteger.class), any(REXPDouble.class))).thenReturn(getTestReturnValue());
46 |
47 | List result = service.groupValues(newList(new double[]{1d,2d,3d,4d}));
48 |
49 | assertThat(result).hasSize(2);
50 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4});
51 | }
52 |
53 | @Test
54 | public void testBlockFunctionWithSeveralVariables() throws Exception{
55 |
56 | when(engineProvider.blockFunction(any(REXPInteger.class), anyVararg())).thenReturn(getTestReturnValue());
57 |
58 | List result = service.groupValues(newList(new double[]{1d,2d,3d,4d}, new double[]{-1d,-2d,-3d,-4d}));
59 |
60 | assertThat(result).hasSize(2);
61 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4});
62 | ArgumentCaptor continuousValues = ArgumentCaptor.forClass(REXPDouble.class);
63 | verify(engineProvider, times(1)).blockFunction(any(REXPInteger.class), continuousValues.capture());
64 | assertThat(continuousValues.getAllValues().get(0).asDoubles()).containsExactly(1,2,3,4);
65 | assertThat(continuousValues.getAllValues().get(1).asDoubles()).containsExactly(-1,-2,-3,-4);
66 | }
67 |
68 | @Test
69 | public void testBlockDiscreteFunction() throws Exception{
70 |
71 | when(engineProvider.blockDiscreteFunction(any(REXPInteger.class), any(REXPString.class))).thenReturn(getTestReturnValue());
72 |
73 | List result = service.groupDiscreteValues(buildList(new String[]{"A","B","C","D"}));
74 |
75 | assertThat(result).hasSize(2);
76 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4});
77 | ArgumentCaptor discreteValues = ArgumentCaptor.forClass(REXPString.class);
78 | verify(engineProvider, times(1)).blockDiscreteFunction(any(REXPInteger.class), discreteValues.capture());
79 | assertThat(discreteValues.getValue().asStrings()).containsExactly("A", "B", "C", "D");
80 | }
81 |
82 | @Test
83 | public void testBlockDiscreteFunctionWithSeveralVariables() throws Exception{
84 |
85 | when(engineProvider.blockDiscreteFunction(any(REXPInteger.class), anyVararg())).thenReturn(getTestReturnValue());
86 |
87 | List result = service.groupDiscreteValues(newList(new String[]{"A", "B", "C", "D"}, new String[]{"E", "F", "G", "H"}));
88 |
89 | assertThat(result).hasSize(2);
90 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4});
91 | ArgumentCaptor discreteValues = ArgumentCaptor.forClass(REXPString.class);
92 | verify(engineProvider, times(1)).blockDiscreteFunction(any(REXPInteger.class), discreteValues.capture());
93 | assertThat(discreteValues.getAllValues().get(0).asStrings()).containsExactly("A", "B", "C", "D");
94 | assertThat(discreteValues.getAllValues().get(1).asStrings()).containsExactly("E", "F", "G", "H");
95 | }
96 |
97 | private REXP getTestReturnValue() {
98 | RList values = new RList();
99 | values.add(new REXPString(new String[]{"1","2"}));
100 | values.add(new REXPString(new String[]{"3","4"}));
101 | values.add(new REXPDouble(new double[]{0.098,1.234}));
102 | REXPGenericVector vector = new REXPGenericVector(values);
103 |
104 | return vector;
105 | }
106 |
107 | @Test
108 | public void testBlockFunctionWithOddNumberOfValues() throws Exception{
109 |
110 | when(engineProvider.blockFunction(any(REXPInteger.class), any(REXPDouble.class))).thenReturn(getTestReturnOddValues());
111 |
112 | List result = service.groupValues(newList(new double[]{1d,2d,3d,4d,5d}));
113 |
114 | assertThat(result).hasSize(3);
115 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4}, new int[]{5});
116 | }
117 |
118 | private REXP getTestReturnOddValues() {
119 | RList values = new RList();
120 | values.add(new REXPString(new String[]{"1","2","5"}));
121 | values.add(new REXPString(new String[]{"3","4",null}));
122 | values.add(new REXPDouble(new double[]{0.098,1.234, Double.NaN}));
123 | REXPGenericVector vector = new REXPGenericVector(values);
124 |
125 | return vector;
126 | }
127 |
128 | @Test
129 | public void testBlockFunctionUsingOneDiscreteAndOneContinuousVariableCallsEngine() {
130 |
131 | configureMockEngineBlockGeneralFunctionToReturnEvenResults();
132 |
133 | List result = service.groupMultipleValues(
134 | buildList(new String[]{"A", "B", "C", "D"}),
135 | newList(new double[]{1d,2d,3d,4d})
136 | );
137 |
138 | verify(engineProvider, times(1))
139 | .blockGeneralFunction(
140 | any(REXPInteger.class),
141 | anyListOf(REXPString.class),
142 | anyListOf(REXPDouble.class)
143 | );
144 |
145 | assertThat(result).hasSize(2);
146 | assertThat(result).containsExactly(new int[]{1,3}, new int[]{2,4});
147 | }
148 |
149 | private void configureMockEngineBlockGeneralFunctionToReturnEvenResults() {
150 | when(
151 | engineProvider
152 | .blockGeneralFunction(any(REXPInteger.class),
153 | anyListOf(REXPString.class),
154 | anyListOf(REXPDouble.class))
155 | ).thenReturn(getTestReturnValue());
156 | }
157 |
158 | @Test
159 | public void testBlockFunctionUsingOneDiscreteAndOneContinuousVariableGeneratesIds() {
160 | configureMockEngineBlockGeneralFunctionToReturnEvenResults();
161 |
162 | service.groupMultipleValues(
163 | buildList(new String[]{"A", "B", "C", "D"}),
164 | newList(new double[]{1d,2d,3d,4d})
165 | );
166 |
167 | ArgumentCaptor ids = ArgumentCaptor.forClass(REXPInteger.class);
168 | verify(engineProvider, times(1))
169 | .blockGeneralFunction(
170 | ids.capture(),
171 | anyListOf(REXPString.class),
172 | anyListOf(REXPDouble.class)
173 | );
174 |
175 | assertThat(ids.getAllValues()).hasSize(1);
176 | assertThat(ids.getValue().asIntegers()).containsExactly(0, 1, 2, 3);
177 | }
178 |
179 | @SuppressWarnings({ "unchecked", "rawtypes" })
180 | @Test
181 | public void testBlockFunctionUsingOneDiscreteAndOneContinuousVariableGeneratesDiscreteNativeValues() {
182 |
183 | configureMockEngineBlockGeneralFunctionToReturnEvenResults();
184 |
185 | service.groupMultipleValues(
186 | buildList(new String[]{"A", "B", "C", "D"}),
187 | newList(new double[]{1d,2d,3d,4d})
188 | );
189 |
190 | ArgumentCaptor discreteValues = ArgumentCaptor.forClass(List.class);
191 | verify(engineProvider, times(1))
192 | .blockGeneralFunction(
193 | any(REXPInteger.class),
194 | discreteValues.capture(),
195 | anyListOf(REXPDouble.class)
196 | );
197 |
198 | assertThat(discreteValues.getAllValues()).hasSize(1);
199 | assertThat(discreteValues.getValue()).hasSize(1);
200 | assertThat(discreteValues.getValue().get(0)).isInstanceOf(REXPString.class);
201 | assertThat(((REXPString)discreteValues.getValue().get(0)).asStrings()).containsExactly("A","B","C","D");
202 | }
203 |
204 | @SuppressWarnings({ "unchecked", "rawtypes" })
205 | @Test
206 | public void testBlockFunctionUsingOneDiscreteAndOneContinuousVariableGeneratesContinuousNativeValues() {
207 |
208 | configureMockEngineBlockGeneralFunctionToReturnEvenResults();
209 |
210 | service.groupMultipleValues(
211 | buildList(new String[]{"A", "B", "C", "D"}),
212 | newList(new double[]{1d,2d,3d,4d})
213 | );
214 |
215 | ArgumentCaptor continuousValues = ArgumentCaptor.forClass(List.class);
216 | verify(engineProvider, times(1))
217 | .blockGeneralFunction(
218 | any(REXPInteger.class),
219 | anyListOf(REXPString.class),
220 | continuousValues.capture()
221 | );
222 |
223 | assertThat(continuousValues.getAllValues()).hasSize(1);
224 | assertThat(continuousValues.getValue()).hasSize(1);
225 | assertThat(continuousValues.getValue().get(0)).isInstanceOf(REXPDouble.class);
226 | assertThat(((REXPDouble)continuousValues.getValue().get(0)).asDoubles()).containsExactly(1,2,3,4);
227 | }
228 |
229 | @SuppressWarnings({ "unchecked", "rawtypes" })
230 | @Test
231 | public void testBlockFunctionUsingSeveralDiscreteAndOneContinuousVariableGeneratesDiscreteNativeValues() {
232 |
233 | configureMockEngineBlockGeneralFunctionToReturnEvenResults();
234 |
235 | service.groupMultipleValues(
236 | buildList(
237 | new String[]{"A", "B", "C", "D"},
238 | new String[]{"10", "20", "30", "40"}
239 | ),
240 | newList(new double[]{1d,2d,3d,4d})
241 | );
242 |
243 | ArgumentCaptor discreteValues = ArgumentCaptor.forClass(List.class);
244 | verify(engineProvider, times(1))
245 | .blockGeneralFunction(
246 | any(REXPInteger.class),
247 | discreteValues.capture(),
248 | anyListOf(REXPDouble.class)
249 | );
250 |
251 | assertThat(discreteValues.getAllValues()).hasSize(1);
252 | assertThat(discreteValues.getValue()).hasSize(2);
253 | assertThat(discreteValues.getValue().get(0)).isInstanceOf(REXPString.class);
254 | assertThat(((REXPString)discreteValues.getValue().get(0)).asStrings()).containsExactly("A","B","C","D");
255 | assertThat(((REXPString)discreteValues.getValue().get(1)).asStrings()).containsExactly("10","20","30","40");
256 | }
257 |
258 | @SuppressWarnings({ "unchecked", "rawtypes" })
259 | @Test
260 | public void testBlockFunctionUsingOneDiscreteAndSeveralContinuousVariableGeneratesContinuousNativeValues() {
261 |
262 | configureMockEngineBlockGeneralFunctionToReturnEvenResults();
263 |
264 | service.groupMultipleValues(
265 | buildList(new String[]{"A", "B", "C", "D"}),
266 | newList(
267 | new double[]{1d,2d,3d,4d},
268 | new double[]{-1d,-2d,-3d,-4d}
269 | )
270 | );
271 |
272 | ArgumentCaptor continuousValues = ArgumentCaptor.forClass(List.class);
273 | verify(engineProvider, times(1))
274 | .blockGeneralFunction(
275 | any(REXPInteger.class),
276 | anyListOf(REXPString.class),
277 | continuousValues.capture()
278 | );
279 |
280 | assertThat(continuousValues.getAllValues()).hasSize(1);
281 | assertThat(continuousValues.getValue()).hasSize(2);
282 | assertThat(continuousValues.getValue().get(0)).isInstanceOf(REXPDouble.class);
283 | assertThat(((REXPDouble)continuousValues.getValue().get(0)).asDoubles()).containsExactly(1,2,3,4);
284 | assertThat(((REXPDouble)continuousValues.getValue().get(1)).asDoubles()).containsExactly(-1,-2,-3,-4);
285 | }
286 |
287 | @Test
288 | public void testKolmogorovSmirnovTest() {
289 |
290 | when(engineProvider.ksTest(any(REXPDouble.class), any(REXPDouble.class))).thenReturn(1.0);
291 |
292 | double pValue = service.kolmogorovSmirnovTest(new double[]{1d,2d,3d,4d}, new double[]{1d,2d,3d,4d});
293 |
294 | assertThat(pValue).isGreaterThan(0.05);
295 | }
296 |
297 |
298 | private List buildList(String[]... strings) {
299 | List result = new ArrayList();
300 | for(String[] value : strings) {
301 | result.add(value);
302 | }
303 | return result;
304 | }
305 | }
306 |
--------------------------------------------------------------------------------