Tuesday, February 18, 2014

Map and Reduce methods in Java


For those of you, who have been using Scala for a while and now came back to Java its hard to accept the fact that there is no map and reduce methods available in Java. Lets say you want to write a function that takes a List, and returns the product (of multiplication) of all the Ints in the list. It will be similar to this:
public static int product(List seq){
 int product = 0;
 
 for(int i : seq){
  product *= i;
 }
 
 return product;
}

In Scala you can write the same code in one line:
val result = (1 to 10).fold(0)(_ * _)
How about a method that counts a letters of words in a sentence?
public Collection countWords(Collection words){
 
 Collection result = new ArrayList();
 
 for(String word : words){
  result.add(word.length());
 }
 
 return result;
}

Here is the same logic written in Scala:
words.map(_.length)
So is there a way to do map and reduce in Java?

Yes, that's how I solved this problem. First, you'll need two classes that represents high-order functions of one and two arguments.
public interface Function1 {
 
 public R run(IN param);

}

public interface Function2 {
 
 public R run(IN1 param1, IN2 param2);

}

Next, lets write our map and reduce methods.
public class CollectionUtils {

 public static  R fold(Collection sequence, R initialValue, Function2 action) {

  for (IN element : sequence) {
   initialValue = action.run(initialValue, element);
  }

  return initialValue;
 }

 public static  Collection map(Collection sequence, Function1 action) {
  
  Collection result = new ArrayList<>();

  for (IN element : sequence) {
   result.add(action.run(element));
  }

  return result;
 }

}

Now lets get back to our examples. Here is how you are going to do the product and word count examples with help of those methods. First you'll need two helper functions.
public class Functions {
 
        public static Function2 product() {

  return new Function2() {

   @Override
   public Integer run(Integer param1, Integer param2) {

    return param1 * param2;

   }

  };

 }
 
 public static Function1 countLetters() {

  return new Function1() {

   @Override
   public Integer run(String param1) {

    return param1.length();

   }

  };

 }
 
}
Finally, one-liner that does product of number of letters of all words in a sentence.
CollectionUtils.fold(CollectionUtils.map(Arrays.asList("The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"), Functions.countLetters()), 1, Functions.product());


Tuesday, January 14, 2014

Play Start/Stop Script.

Here is a start/stop script that I use for my Play! 2.2.x applications.

#!/bin/bash

println() {
    echo
    echo $1
    echo
}

get_or_else() {
        if [ "x" = "x$1" ]
          then echo $2
          else echo $1
        fi
}

check_status() {
    if [ -f $PID_FILE_PATH ]
    then
        kill -0 $( cat $PID_FILE_PATH ) 2> /dev/null
        echo $?
    else
        echo 1
    fi
}

STARTUP_CONFIGURATION_FILE_PATH=/etc/default/play

if [ ! -f "$STARTUP_CONFIGURATION_FILE_PATH" ]
then
  println "Please set STARTUP_CONFIGURATION_FILE_PATH variable in the $0"
  exit 1
fi

source $STARTUP_CONFIGURATION_FILE_PATH

if [ ! -d "$BASE_DIR" ]
then
  println "Please set BASE_DIR variable in the $STARTUP_CONFIGURATION_FILE_PATH"
  exit 1
fi

OPTS="-server"
START_DELAY=3
STOP_DELAY=3

CMD=$BASE_DIR/bin/myapp

PORT=$(get_or_else "$PORT" '9000' )
LISTEN_IP=$(get_or_else "$LISTEN_IP" '0.0.0.0' )
PID_FILE_PATH=$(get_or_else "$PID_FILE_PATH" "$BASE_DIR/play.pid" )
STDOUT_LOG=$(get_or_else "$STDOUT_LOG" '/dev/null' )
HEAP_START_SIZE=$(get_or_else "$HEAP_START_SIZE" '128M' )
HEAP_MAX_SIZE=$(get_or_else "$HEAP_MAX_SIZE" '1024M' )

OPTS="$JAVA_OPTS -Dpidfile.path=$PID_FILE_PATH -J-Xms$HEAP_START_SIZE -J-Xmx$HEAP_MAX_SIZE"

if [ "x" != "x$CONFIGURATION_FILE_PATH" ]
then
  OPTS="$OPTS -Dconfig.file=$CONFIGURATION_FILE_PATH"
fi

if [ "x" != "x$LOG_CONFIGURATION_FILE_PATH" ]
then
  OPTS="$OPTS -Dlogger.file=$LOG_CONFIGURATION_FILE_PATH"
fi

COMMAND="$CMD $OPTS -Dhttp.address=$LISTEN_IP -Dhttp.port=$PORT"

status() {
    if [ 0 -eq $(check_status) ]
    then
        println "Play! is running: [$( cat $PID_FILE_PATH )]."
    else
        println "Play! is not running."
    fi
}

start() {
    if [ 0 -eq $(check_status) ]
    then
        println "Play! is running. [$( cat $PID_FILE_PATH )]."
    else

        rm $STDOUT_LOG >/dev/null 2>&1

        nohup $COMMAND >>$STDOUT_LOG 2>&1 &

        sleep $START_DELAY

        if [ 0 -eq $(check_status) ]
        then
                println "Started."
        else
                rm $PID_FILE_PATH >/dev/null 2>&1
                println "Could not start Play!, please see [$STDOUT_LOG] for more details."
        fi
    fi
}

stop() {
    if [ 0 -eq $(check_status) ]
    then

        if kill $( cat $PID_FILE_PATH )
        then

          sleep $STOP_DELAY

          if [ 0 -lt $(check_status) ]
          then
            println "Stopped."
          else
            println "Could not stop Play! process. Please stop it manually."
          fi

        else
          println "Could not stop process $( cat $PID )."
        fi

    else
        println "Play! is not running."
    fi
}

case "$1" in
    'start')
            start
            ;;
    'stop')
            stop
            ;;
    'restart')
            stop ;
            start
            ;;
    'status')
            status
            ;;
    *)
            echo
            echo "Usage: $0 { start | stop | restart | status }"
            echo
            exit 1
            ;;
esac

exit 0


To run this script just change value of the CMD=$BASE_DIR/bin/myapp variable in the script and create your configuration file /etc/default/play. Here is an example of configuration file I have.


#Base directory of the play project
BASE_DIR=/opt/app/myapp

#Path to the file with process ID
#PID_FILE_PATH=$BASE_DIR/play.pid

#Path to the file, where process output will be redirected
STDOUT_LOG=$BASE_DIR/play.out

#Play! configuration file
CONFIGURATION_FILE_PATH=$BASE_DIR/../myapp.conf

#Logging configuration file
LOG_CONFIGURATION_FILE_PATH=$BASE_DIR/../logger.xml

#Play! port
PORT=9090

#Play! host
#LISTEN_IP='127.0.0.1'

#Additional JVM options
#JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
JAVA_OPTS="-DapplyEvolutions.default=true -DapplyDownEvolutions.default=true"

#Start size of JVM heap
HEAP_START_SIZE=1024M

#Maximum size of JVM heap
HEAP_MAX_SIZE=1024M