All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mchange.sc.v2.failable.package.scala Maven / Gradle / Ivy

The newest version!
/*
 * Distributed as part of mchange-commons-scala v0.4.0
 *
 * Copyright (C) 2015 Machinery For Change, Inc.
 *
 * Author: Steve Waldman 
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of EITHER:
 *
 *     1) The GNU Lesser General Public License (LGPL), version 2.1, as 
 *        published by the Free Software Foundation
 *
 * OR
 *
 *     2) The Eclipse Public License (EPL), version 1.0
 *
 * You may choose which license to accept if you wish to redistribute
 * or modify this work. You may offer derivatives of this work
 * under the license you have chosen, or you may provide the same
 * choice of license which you have been offered here.
 *
 * This software 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.
 *
 * You should have received copies of both LGPL v2.1 and EPL v1.0
 * along with this software; see the files LICENSE-EPL and LICENSE-LGPL.
 * If not, the text of these licenses are currently available at
 *
 * LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *  EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php 
 * 
 */

package com.mchange.sc.v2;

import com.mchange.leftright.BiasedEither;

import com.mchange.sc.v1.log.{MLogger,MLevel};
import com.mchange.sc.v1.log.MLevel._;

import scala.util.{Try, Success, Failure};

import scala.language.implicitConversions;

package object failable {
  class UnhandledFailException( fail : Fail ) extends Exception( fail.toString, fail.source match { case th : Throwable => th; case _ => null } );

  val lineSeparator = scala.util.Properties.lineSeparator;

  // kind of yuk, but we've renamed this from "Failure" to "Fail" to avoid inconvenient
  // need to qualify names when working with scala.util.Failure.
  final object Fail {
    def simple( message : String ) : Fail = Fail( message, message, None );
    val EmptyFailable : Fail = Fail("An attempt to filter or pattern-match a Failable failed, leaving EmptyFailable.", "EmptyFailable", None);
  }
  final case class Fail( message : String, source : Any, mbStackTrace : Option[Array[StackTraceElement]] ) {

    override def toString() : String = "Fail:" + mbStackTrace.fold( message ) { stackTrace =>
      (List( message ) ++ stackTrace).mkString( lineSeparator )
    }
    def vomit : Nothing = throw new UnhandledFailException( this );
  }

  trait FailSource[T] {
    def getMessage( source : T ) : String;
    def getStackTrace( source : T ) : Array[StackTraceElement] = Thread.currentThread().getStackTrace();

    def getFail( source : T, includeStackTrace : Boolean = true ) : Fail = {
      val mbStackTrace = if ( includeStackTrace ) Some( getStackTrace( source ) ) else None;
      Fail( getMessage( source ), source, mbStackTrace )
    }
  }

  implicit final object StringAsFailSource extends FailSource[String] {
    def getMessage( source : String ) : String = source;
  }

  implicit final object ThrowableAsFailSource extends FailSource[Throwable] {
    def getMessage( source : Throwable ) : String = s"${source.getClass.getName}: ${source.getMessage()}";

    override def getStackTrace( source : Throwable ) = source.getStackTrace;
  }

  type Failable[+T] = Either[Fail,T];

  val  Succeeded     = Right;
  type Succeeded[+T] = Right[Fail,T];

  val  Failed     = Left;
  type Failed[+T] = Left[Fail,T];

  // right-bias Failable[T], for convenience and to render its API more analogous to Option[T]
  private val FailableAsMonad = BiasedEither.RightBias.withEmptyToken[Fail]( Fail.EmptyFailable );
  implicit final class FailableOps[T]( failable : Failable[T] ) extends BiasedEither.RightBias.withEmptyToken.AbstractOps( failable )( FailableAsMonad ) {
    override def get : T = failable match {
      case Left( fail )   => fail.vomit;
      case Right( value ) => value;
    }
    def fail : Fail = failable.left.get;
    //other methods
    def flatten[U](implicit evidence : T <:< Failable[U]) : Failable[U] = {
      failable match {
        case oops @ Left( _ )  => refail( oops );
        case        Right( t ) => evidence( t );
      }
    }
    def recover[TT >: T]( f : Fail => TT ) : Failable[TT] = {
      failable match {
        case      Left( fail ) => succeed( f( fail ) )
        case ok @ Right( _ )   => ok;
      }
    }
    def recover[TT >: T]( recoveryValue : TT ) : Failable[TT] = recover( _ => recoveryValue )

    def isSucceeded : Boolean = failable.isRight;
    def isFailed    : Boolean = !isSucceeded;

    def asSucceeded[TT >: T] : Succeeded[TT] = failable.asInstanceOf[Succeeded[TT]]
    def asFailed[TT >: T] : Failed[TT]       = failable.asInstanceOf[Failed[TT]] 

    def toWarnable( recoveryFunction : Fail => T ) : Warnable[T] = {
      def recoveryWarnable( oops : Fail ) = {
        val recoveryValue = recoveryFunction( oops );
        Warnable[T]( List( Fail.simple(s"Using recovery value: ${recoveryValue}"), oops ), recoveryValue );
      }
      failable.fold( recoveryWarnable _, t => Warnable[T]( Nil, t ) )
    }
    def toWarnable( recovery : T ) : Warnable[T] = {
      def recoveryWarnable( oops : Fail ) = Warnable[T]( List( Fail.simple(s"Using recovery default: ${recovery}"), oops ), recovery );
      failable.fold( recoveryWarnable _, t => Warnable[T]( Nil, t ) )
    }
  }

  def fail[S : FailSource]( source : S, includeStackTrace : Boolean = true ) : Failable[Nothing] = {
    val ms = implicitly[FailSource[S]];
    val failure = ms.getFail( source, includeStackTrace );
    Left( failure ) : Failable[Nothing];
  }

  /**
   * A utility to re-establish the irrelevant right type as universally acceptable Nothing
   */  
  def refail( prefail : Failed[Any] ) : Failable[Nothing] = prefail.asInstanceOf[Failable[Nothing]]

  def succeed[T]( value : T ) : Failable[T] = Right( value );

  val Poop : PartialFunction[Throwable, Failable[Nothing]] = { case scala.util.control.NonFatal( t : Throwable ) => fail( t ) }

  implicit class FailableTry[T]( val attempt : Try[T] ) extends AnyVal {
    def toFailable : Failable[T] = attempt match {
      case Success( value )     => succeed( value );
      case Failure( exception ) => fail( exception, true );
    }
  }

  implicit class FailableOption[T]( val maybe : Option[T] ) extends AnyVal {
    def toFailable[ U : FailSource ]( source : U = "No information available." ) : Failable[T] = {
      maybe match {
        case Some( value )  => succeed( value );
        case None           => fail( source, true );
      }
    }
  }

  implicit class FailableLoggingOps[T]( val failable : Failable[T] ) extends AnyVal {
    def log( level : MLevel, premessage : => String  = "" )( implicit logger : MLogger ) : Failable[T] = {
      def doLog( oops : Fail ) = {
        val pm = premessage; // avoid multiple executions of the by name expression
        val prefix = if ( pm == "" || pm == null ) "" else pm + lineSeparator;
        level.log( prefix + oops )( logger )
      }
      failable match {
        case Left( oops ) => doLog( oops );
        case Right( _ )   => /* ignore */;
      }
      failable
    }
    def logRecover[TT >: T]( level : MLevel, recoveryFunction : Fail => TT, premessage : => String )( implicit logger : MLogger ) : Failable[TT] = {
      log( level, premessage )( logger ).recover( recoveryFunction );
    }
    def logRecover[TT >: T]( level : MLevel, recoveryFunction : Fail => TT )( implicit logger : MLogger ) : Failable[TT] = logRecover[TT]( level, recoveryFunction, "" )( logger );

    def logRecover[TT >: T]( level : MLevel, recoveryValue : TT, premessage : => String )( implicit logger : MLogger ) : Failable[TT] = {
      log( level, premessage )( logger ).recover( recoveryValue );
    }
    def logRecover[TT >: T]( level : MLevel, recoveryValue : TT )( implicit logger : MLogger ) : Failable[TT] = logRecover( level, recoveryValue, "" )( logger );

    // is the API below just a little too cute?
    def warning( premessage : => String = "" )( implicit logger : MLogger ) : Failable[T] = log( WARNING, premessage )( logger )
    def severe( premessage : => String = "" )( implicit logger : MLogger )  : Failable[T] = log( SEVERE, premessage )( logger )
    def info( premessage : => String = "" )( implicit logger : MLogger )    : Failable[T] = log( INFO, premessage )( logger )
    def debug( premessage : => String = "" )( implicit logger : MLogger )   : Failable[T] = log( DEBUG, premessage )( logger )
    def trace( premessage : => String = "" )( implicit logger : MLogger )   : Failable[T] = log( TRACE, premessage )( logger )

    def warn( premessage : => String = "" )( implicit logger : MLogger ) : Failable[T] = warning( premessage )( logger )
  }

  case class Warnable[+T]( warnings : List[Fail], result : T ) {
    def map[Y]( f : T => Y )               : Warnable[Y] = Warnable[Y]( this.warnings, f( result ) );
    def flatMap[Y]( f : T => Warnable[Y] ) : Warnable[Y] = map( f ).flatten;
    def clearWarnings                      : Warnable[T] = this.copy( warnings=Nil );
  }
  implicit class NestingWarnableOps[Y]( val nesting : Warnable[Warnable[Y]] ) extends AnyVal {
    def flatten : Warnable[Y] = Warnable[Y]( nesting.result.warnings ::: nesting.warnings, nesting.result.result );
  }

  implicit class WarnableLoggingOps[T]( val warnable : Warnable[T] ) extends AnyVal {
    private def logWarnings( level : MLevel, premessage : =>String, tag : =>String, clear : Boolean )( implicit logger : MLogger ) : Warnable[T] = {
      if (! warnable.warnings.isEmpty ) { // avoid computation of by-name premessage if there is nothing to warn
        val pm = premessage;              // avoid multiple computations of by-name premessage
        val t = tag;
        val linePrefix = if ( t == null ) "Execution Warning -> " else t;
        if (pm != null && pm != "") level.log( pm );
        warnable.warnings.foreach( oops => level.log( linePrefix + oops )( logger ) );
        if (clear) warnable.clearWarnings else warnable
      } else {
        warnable
      }
    }
    def log( level : MLevel = WARNING, premessage : =>String = null, tag : =>String = null )( implicit logger : MLogger ) = logWarnings( level, premessage, tag, false )( logger );
    def logClear( level : MLevel = WARNING, premessage : =>String = null, tag : =>String = null )( implicit logger : MLogger ) = logWarnings( level, premessage, tag, true )( logger );

    def debugLog( premessage : =>String = null, tag : =>String = null )( implicit logger : MLogger ) = log( DEBUG, premessage, tag )( logger );
    def debugLogClear( premessage : =>String = null, tag : =>String = null )( implicit logger : MLogger ) = logClear( DEBUG, premessage, tag )( logger );
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy