code: /***************************************************************************
   *   Copyright (C) by Fabrizio Montesi                                     *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
   *   it under the terms of the GNU Library General Public License as       *
   *   published by the Free Software Foundation; either version 2 of the    *
   *   License, or (at your option) any later version.                       *
   *                                                                         *
   *   This program is distributed in the hope that it will be useful,       *
   *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
   *   GNU General Public License for more details.                          *
   *                                                                         *
   *   You should have received a copy of the GNU Library General Public     *
   *   License along with this program; if not, write to the                 *
   *   Free Software Foundation, Inc.,                                       *
   *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
   *                                                                         *
   *   For details about the authors of this software, see the AUTHORS file. *
   ***************************************************************************/
  
  package joliex.util;
  
  
  import java.text.DateFormat;
  import java.text.ParseException;
  import java.text.SimpleDateFormat;
  import java.util.*;
  import java.util.concurrent.*;
  import java.util.concurrent.atomic.AtomicLong;
  import jolie.net.CommMessage;
  import jolie.runtime.FaultException;
  import jolie.runtime.JavaService;
  import jolie.runtime.Value;
  import jolie.runtime.ValueVector;
  import jolie.runtime.embedding.RequestResponse;
  
  public class TimeService extends JavaService
  {
  	protected class TimeThread extends Thread
  	{
  		private final long waitTime;
  		private final String callbackOperation;
  		private final Value callbackValue;
  		private final TimeService parent;
  		public TimeThread( TimeService parent, long waitTime, String callbackOperation, Value callbackValue )
  		{
  			this.waitTime = waitTime;
  			this.callbackOperation =
  					( callbackOperation == null ) ? "timeout" : callbackOperation;
  			this.callbackValue =
  					( callbackValue == null ) ? Value.create() : callbackValue;
  			this.parent = parent;
  		}
  
  		@Override
  		public void run()
  		{
  			try {
  				Thread.sleep( waitTime );
  				parent.sendMessage( CommMessage.createRequest( callbackOperation, "/", callbackValue ) );
  			} catch( InterruptedException e ) {}
  		}
  	}
  
  	private TimeThread thread = null;
  	private final DateFormat dateFormat, dateTimeFormat;
  	private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
  	private final Map<Long, ScheduledFuture> scheduledFutureHashMap = new ConcurrentHashMap<>();
  	private final AtomicLong atomicLong = new AtomicLong();
  
  	public TimeService()
  	{
  		dateFormat = DateFormat.getDateInstance( DateFormat.SHORT );
  		dateTimeFormat = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.MEDIUM );
  	}
  
  	@Override
  	protected void finalize()
  		throws Throwable
  	{
  		try {
  			if ( thread != null ) {
  				thread.interrupt();
  			}
  		} finally {
  			super.finalize();
  		}
  	}
  
  	private void launchTimeThread( long waitTime, String callbackOperation, Value callbackValue )
  	{
  		waitTime = ( waitTime > 0 ) ? waitTime : 0L;
  		if ( thread != null ) {
  			thread.interrupt();
  		}
  		thread = new TimeThread( this, waitTime, callbackOperation, callbackValue );
  		thread.start();
  	}
  
  	public void setNextTimeout( Value request )
  	{
  		long waitTime = request.intValue();
  		String callbackOperation = null;
  		ValueVector vec;
  		Value callbackValue = null;
  		if ( (vec=request.children().get( "operation" )) != null ) {
  			callbackOperation = vec.first().strValue();
  		}
  		if ( (vec=request.children().get( "message" )) != null ) {
  			callbackValue = vec.first();
  		}
  
  		launchTimeThread( waitTime, callbackOperation, callbackValue );
  	}
  
  	public void setNextTimeoutByDateTime( Value request )
  	{
  		long waitTime = 0;
  		try {
  			synchronized( dateTimeFormat ) {
  				Date date = dateTimeFormat.parse( request.strValue() );
  				waitTime = date.getTime() - (new Date()).getTime();
  			}
  		} catch( ParseException e ) {}
  
  		String callbackOperation = null;
  		ValueVector vec;
  		Value callbackValue = null;
  		if ( (vec=request.children().get( "operation" )) != null ) {
  			callbackOperation = vec.first().strValue();
  		}
  		if ( (vec=request.children().get( "message" )) != null ) {
  			callbackValue = vec.first();
  		}
  
  		launchTimeThread( waitTime, callbackOperation, callbackValue );
  	}
  
  	public void setNextTimeoutByTime( Value request )
  	{
  		long waitTime = 0;
  		try {
  			synchronized( dateTimeFormat ) {
  				String today = dateFormat.format( new Date() );
  				Date date = dateTimeFormat.parse( today + " " + request.strValue() );
  				waitTime = date.getTime() - (new Date()).getTime();
  			}
  		} catch( ParseException pe ) {}
  
  		String callbackOperation = null;
  		ValueVector vec;
  		Value callbackValue = null;
  		if ( (vec=request.children().get( "operation" )) != null )
  			callbackOperation = vec.first().strValue();
  		if ( (vec=request.children().get( "message" )) != null )
  			callbackValue = vec.first();
  
  		launchTimeThread( waitTime, callbackOperation, callbackValue );
  	}
  
  	@RequestResponse
  	public void sleep( Integer millis )
  	{
  		try {
  			if ( millis > 0 ) {
  				Thread.sleep( millis );
  			}
  		} catch ( InterruptedException e ) {
  		}
  	}
  
  	public Long getCurrentTimeMillis()
  	{
  		return System.currentTimeMillis();
  	}
  
  	public String getCurrentDateTime( Value request )
  	{
  		String result = null;
  		try {
  			String format;
  			if ( request.getFirstChild( "format" ).strValue().isEmpty() ) {
  				format = "dd/MM/yyyy HH:mm:ss";
  			} else {
  				format = request.getFirstChild( "format" ).strValue();
  			}
  			SimpleDateFormat sdf = new SimpleDateFormat( format );
  			final Date now = new Date();
  			result = sdf.format( now );
  		} catch( Exception e ) {
  			e.printStackTrace(); // TODO FaultException
  		}
  		return result;
  	}
  
  
  	public Value getDateTime( Value request )
  	{
  		Value result = Value.create();
  		try {
  			String format;
  			if ( request.getFirstChild( "format" ).strValue().isEmpty() ) {
  				format = "dd/MM/yyyy HH:mm:ss";
  			} else {
  				format = request.getFirstChild( "format" ).strValue();
  			}
  			long tm = request.longValue();
  			SimpleDateFormat sdf = new SimpleDateFormat( format );
  			final Date timestamp = new Date( tm );
  			result.setValue(sdf.format( timestamp ));
  			GregorianCalendar cal = new GregorianCalendar();
  			cal.setTimeInMillis( timestamp.getTime() );
  			result.getFirstChild( "day" ).setValue( cal.get( Calendar.DAY_OF_MONTH ) );
  			result.getFirstChild( "month" ).setValue( cal.get( Calendar.MONTH ) + 1 );
  			result.getFirstChild( "year" ).setValue( cal.get( Calendar.YEAR ) );
  			result.getFirstChild( "hour" ).setValue( cal.get( Calendar.HOUR_OF_DAY ) );
  			result.getFirstChild( "minute" ).setValue( cal.get( Calendar.MINUTE ) );
  			result.getFirstChild( "second" ).setValue( cal.get( Calendar.SECOND ) );
  		} catch( Exception e ) {
  			e.printStackTrace(); // TODO FaultException
  		}
  		return result;
  	}
  
  	/**
  	 * @author Claudio Guidi
  	 */
  	public Value getCurrentDateValues()
  	{
  		Value v = Value.create();
  
  		GregorianCalendar cal = new GregorianCalendar();
  		cal.setTimeInMillis( new Date().getTime() );
  		v.getFirstChild( "day" ).setValue( cal.get( Calendar.DAY_OF_MONTH ) );
  		v.getFirstChild( "month" ).setValue( cal.get( Calendar.MONTH ) + 1 );
  		v.getFirstChild( "year" ).setValue( cal.get( Calendar.YEAR ) );
  
  		return v;
  	}
  
  	/**
  	 * @author Claudio Guidi
  	 * @param request
  	 * @return
  	 * @throws jolie.runtime.FaultException
  	 */
  	public Value getDateValues( Value request )
  		throws FaultException
  	{
  		Value v = Value.create();
  		try {
  			String format;
  			if ( request.getFirstChild( "format" ).strValue().isEmpty() ) {
  				format = "dd/MM/yyyy";
  			} else {
  				format = request.getFirstChild( "format" ).strValue();
  			}
  			SimpleDateFormat sdf = new SimpleDateFormat( format );
  			GregorianCalendar cal = new GregorianCalendar();
  			final Date dt = sdf.parse( request.strValue() );
  			cal.setTimeInMillis( dt.getTime() );
  			v.getFirstChild( "day" ).setValue( cal.get( Calendar.DAY_OF_MONTH ) );
  			v.getFirstChild( "month" ).setValue( cal.get( Calendar.MONTH ) + 1 );
  			v.getFirstChild( "year" ).setValue( cal.get( Calendar.YEAR ) );
  		} catch( ParseException pe ) {
  			throw new FaultException( "InvalidDate", pe );
  		}
  
  		return v;
  	}
  
  	/**
  	 * @author Balint Maschio
  	 * @param request
  	 * @return
  	 * @throws jolie.runtime.FaultException
  	 */
  	public Value getDateTimeValues( Value request )
  		throws FaultException
  	{
  		SimpleDateFormat sdf;
  		Value v = Value.create();
  		try {
  			String format;
  			if ( request.getFirstChild( "format" ).strValue().isEmpty() ) {
  				format = "dd/MM/yyyy hh:ss:mm";
  			} else {
  				format = request.getFirstChild( "format" ).strValue();
  			}
  			if ( request.hasChildren( "language" ) ) {
  				String language = request.getFirstChild( "language" ).strValue();
  				if ( language.equals( "us" ) ) {
  					sdf = new SimpleDateFormat( format, Locale.ENGLISH );
  				} else if ( language.equals( "it" ) ) {
  					sdf = new SimpleDateFormat( format, Locale.ITALIAN );
  				} else {
  					sdf = new SimpleDateFormat( format, Locale.US );
  				}
  			} else {
  				sdf = new SimpleDateFormat( format, Locale.US );
  			}
  
  			GregorianCalendar cal = new GregorianCalendar();
  			final Date dt = sdf.parse( request.strValue() );
  			cal.setTimeInMillis( dt.getTime() );
  			v.getFirstChild( "day" ).setValue( cal.get( Calendar.DAY_OF_MONTH ) );
  			v.getFirstChild( "month" ).setValue( cal.get( Calendar.MONTH ) + 1 );
  			v.getFirstChild( "year" ).setValue( cal.get( Calendar.YEAR ) );
  			v.getFirstChild( "hour" ).setValue( cal.get( Calendar.HOUR ) );
  			v.getFirstChild( "minute" ).setValue( cal.get( Calendar.MINUTE ) );
  			v.getFirstChild( "second" ).setValue( cal.get( Calendar.SECOND ) );
  
  		} catch( ParseException pe ) {
  			throw new FaultException( "InvalidDate", pe );
  		}
  
  		return v;
  	}
  
  	/**
  	 * @author Balint Maschio
  	 * 10/2011 - Fabrizio Montesi: convert to using IllegalArgumentException
  	 * instead of regular expressions.
  	 */
  	public Value getTimeValues( Value request )
  		throws FaultException
  	{
  		try {
  			Value v = Value.create();
  			DateFormat sdf = new SimpleDateFormat( "kk:mm:ss" );
  			Date date = sdf.parse( request.strValue() );
  			Calendar calendar = Calendar.getInstance();
  			calendar.setTimeInMillis( date.getTime() );
  			v.getFirstChild( "hour" ).setValue( calendar.get( Calendar.HOUR_OF_DAY ) );
  			v.getFirstChild( "minute" ).setValue( calendar.get( Calendar.MINUTE ) );
  			v.getFirstChild( "second" ).setValue( calendar.get( Calendar.SECOND ) );
  			return v;
  		} catch( ParseException e ) {
  			throw new FaultException( "InvalidTime", e );
  		}
  	}
  	/**
  	 * @author Claudio Guidi
  	 * 10/2010 - Fabrizio Montesi: some optimizations.
  	 */
  	public Value getDateDiff( Value request )
  		throws FaultException
  	{
  		Value v = Value.create();
  		try {
  			String format;
  			if ( request.hasChildren( "format" ) ) {
  				format = request.getFirstChild( "format" ).strValue();
  			} else {
  				format = "dd/MM/yyyy";
  			}
  			SimpleDateFormat sdf = new SimpleDateFormat( format );
  			final Date dt1 = sdf.parse( request.getFirstChild( "date1" ).strValue() );
  			final Date dt2 = sdf.parse( request.getFirstChild( "date2" ).strValue() );
  			Long result = (dt1.getTime() - dt2.getTime()) / (1000 * 60 * 60 * 24);
  			v.setValue( result.intValue() );
  		} catch( ParseException pe ) {
  			throw new FaultException( "InvalidDate", pe );
  		}
  		return v;
  	}
  
  	public Value getTimeDiff( Value request )
  		throws FaultException
  	{
  		Value v = Value.create();
  		try {
  
  			DateFormat sdf = new SimpleDateFormat( "kk:mm:ss" );
  			final Date dt1 = sdf.parse( request.getFirstChild( "time1" ).strValue() );
  			final Date dt2 = sdf.parse( request.getFirstChild( "time2" ).strValue() );
  
  			Long result = new Long( (dt1.getTime() - dt2.getTime()) );
  			v.setValue( result.intValue() );
  		} catch( ParseException pe ) {
  			throw new FaultException( "InvalidDate", pe );
  		}
  		return v;
  	}
  
  	public Value getTimeFromMilliSeconds( Value request )
  		throws FaultException
  	{
  		Value v = Value.create();
  		TimeZone timeZone = TimeZone.getTimeZone( "GMT" );
  
  		Calendar calendar = Calendar.getInstance( timeZone );
  		calendar.setTimeInMillis( request.longValue() );
  		v.getFirstChild( "hour" ).setValue( calendar.get( Calendar.HOUR_OF_DAY ) );
  		v.getFirstChild( "minute" ).setValue( calendar.get( Calendar.MINUTE ) );
  		v.getFirstChild( "second" ).setValue( calendar.get( Calendar.SECOND ) );
  		return v;
  	}
  
  	public Long getTimestampFromString( Value request )
  		throws FaultException
  	{
  		try {
  			String format;
  			if ( request.getFirstChild( "format" ).strValue().isEmpty() ) {
  				format = "dd/MM/yyyy kk:mm:ss";
  			} else {
  				format = request.getFirstChild( "format" ).strValue();
  			}
  			SimpleDateFormat sdf = new SimpleDateFormat( format );
  			final Date dt = sdf.parse( request.strValue() );
  
  			return dt.getTime();
  		} catch( ParseException pe ) {
  			throw new FaultException( "InvalidTimestamp", pe );
  		}
  	}
  
  	@RequestResponse
  	public Long scheduleTimeout( Value request )
  		throws FaultException
  	{
  		final long timeoutId = atomicLong.getAndIncrement();
  		TimeUnit unit;
  
  		if (request.hasChildren( "timeunit" )) {
  			try {
  				unit = TimeUnit.valueOf( request.getFirstChild( "timeunit" ).strValue().toUpperCase() );
  			} catch ( Exception e ) {
  				throw new FaultException( "InvalidTimeUnit", e );
  			}
  		} else {
  			unit = TimeUnit.MILLISECONDS;
  		}
  
  		String operationName;
  		if (request.hasChildren( "operation" )) {
  			operationName = request.getFirstChild( "operation" ).strValue();
  		} else {
  			operationName = "timeout";
  		}
  
  		ScheduledFuture scheduledFuture = executor.schedule( () -> {
  			sendMessage( CommMessage.createRequest( operationName, "/", request.getFirstChild( "message" ) ) );
  		}, request.intValue(), unit );
  		scheduledFutureHashMap.put(timeoutId, scheduledFuture);
  		return timeoutId;
  	}
  
  	@RequestResponse
  	public Boolean cancelTimeout( Value request )
  	{
  		long timeoutId = request.longValue();
  		ScheduledFuture f = scheduledFutureHashMap.remove( timeoutId );
  		return f != null && f.cancel( false );
  	}
  }
  