├── project ├── plugins.sbt └── build.properties ├── version.sbt ├── .gitignore ├── .github └── workflows │ └── build.yml ├── src ├── test │ └── scala │ │ └── io │ │ └── github │ │ └── reugn │ │ └── statecharts │ │ ├── FSMTest.scala │ │ └── UMLTest.scala └── main │ └── scala │ └── io │ └── github │ └── reugn │ └── statecharts │ ├── uml │ ├── Block.scala │ └── UML.scala │ └── fsm │ └── FSM.scala ├── README.md └── LICENSE /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.5.5 -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.2.1" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | project/project 2 | project/target 3 | target 4 | *.class 5 | 6 | *.iml 7 | *.ipr 8 | *.iws 9 | .idea 10 | out 11 | 12 | tags 13 | .*.swp 14 | .*.swo 15 | 16 | build 17 | .classpath 18 | .project 19 | .settings 20 | .bsp 21 | 22 | logs 23 | .DS_Store -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | pull_request: 4 | branches: [ '**' ] 5 | push: 6 | branches: [ '**' ] 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ ubuntu-latest ] 14 | scala: [ 2.12.14, 2.13.6 ] 15 | java: 16 | - adopt@1.8 17 | platform: [ jvm ] 18 | 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v2 24 | 25 | - name: Setup Java and Scala 26 | uses: olafurpg/setup-scala@v12 27 | with: 28 | java-version: ${{ matrix.java }} 29 | 30 | - name: Cache sbt 31 | uses: actions/cache@v2 32 | with: 33 | path: | 34 | ~/.sbt 35 | ~/.ivy2/cache 36 | ~/.coursier/cache/v1 37 | ~/.cache/coursier/v1 38 | ~/AppData/Local/Coursier/Cache/v1 39 | ~/Library/Caches/Coursier/v1 40 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 41 | 42 | - name: Build and test 43 | run: sbt ++${{ matrix.scala }} test -------------------------------------------------------------------------------- /src/test/scala/io/github/reugn/statecharts/FSMTest.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.statecharts 2 | 3 | import io.github.reugn.statecharts.fsm._ 4 | import org.scalatest.flatspec.AnyFlatSpec 5 | import org.scalatest.matchers.should.Matchers 6 | 7 | class FSMTest extends AnyFlatSpec with Matchers { 8 | 9 | behavior of "FSM" 10 | 11 | it should "transition FSM properly" in { 12 | sealed trait FooState extends SideEffect 13 | object Foo1 extends FooState 14 | object Foo2 extends FooState 15 | object Foo3 extends FooState 16 | 17 | sealed trait BarEvent 18 | object Bar1 extends BarEvent 19 | object Bar2 extends BarEvent 20 | object Bar3 extends BarEvent 21 | 22 | val fsm = FSM( 23 | Foo1, 24 | State( 25 | Foo1, 26 | On(Bar1, Foo2), 27 | On(Bar2, Foo3) 28 | ), 29 | State( 30 | Foo2, 31 | On(Bar1, Foo3), 32 | On(Bar2, Foo1) 33 | ), 34 | State( 35 | Foo3, 36 | On(Bar1, Foo1), 37 | On(Bar2, Foo2) 38 | ) 39 | ) 40 | 41 | fsm.state() shouldBe Foo1 42 | fsm.transition(Bar1) shouldBe Foo2 43 | fsm.transition(Bar1) shouldBe Foo3 44 | 45 | intercept[InvalidFSMTransition] { 46 | fsm.transition(Bar3) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scala-statecharts 2 | [![Build](https://github.com/reugn/scala-statecharts/actions/workflows/build.yml/badge.svg)](https://github.com/reugn/scala-statecharts/actions/workflows/build.yml) 3 | 4 | A statecharts library written in Scala. 5 | 6 | ## Implemented 7 | * **FSM** - Finite-state machine. 8 | * One state is defined as the initial state. When a machine starts to execute, it automatically enters this state 9 | * Each state can define actions that occur when a machine enters or exits that state. Actions will typically have side effects 10 | * Each state can define events that trigger a transition 11 | * A transition defines how a machine would react to the event, by exiting one state and entering another state 12 | * A transition can define actions that occur when the transition happens. Actions will typically have side effects 13 | 14 | * **UML** - Executable UML statechart. 15 | * Asynchronous statechart processing 16 | * Recovery capability (start from last successful transition) 17 | * No need to translate diagrams into code 18 | * No bugs introduced by hand translation of diagrams 19 | 20 | ## Getting started 21 | The library is available for the JVM Runtime using Scala 2.12, 2.13. 22 | 23 | Build from source: 24 | ```sh 25 | sbt clean +package 26 | ``` 27 | 28 | ## Examples 29 | * [FSM](./src/test/scala/io/github/reugn/statecharts/FSMTest.scala) 30 | * [UML](./src/test/scala/io/github/reugn/statecharts/UMLTest.scala) 31 | 32 | ## License 33 | Licensed under the [Apache 2.0 License](./LICENSE). 34 | -------------------------------------------------------------------------------- /src/test/scala/io/github/reugn/statecharts/UMLTest.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.statecharts 2 | 3 | import io.github.reugn.statecharts.uml.{ActionBlock, ConditionBlock, UML} 4 | import org.scalatest.flatspec.AsyncFlatSpec 5 | import org.scalatest.matchers.should.Matchers 6 | 7 | import scala.concurrent.Future 8 | import scala.language.postfixOps 9 | 10 | class UMLTest extends AsyncFlatSpec with Matchers { 11 | 12 | behavior of "UML" 13 | 14 | it should "process UML flow properly" in { 15 | val block1 = ActionBlock[String] { 16 | _.map { 17 | in => 18 | "block1" + in 19 | } 20 | 21 | } 22 | val block2 = ActionBlock[String] { 23 | _.map { 24 | in => 25 | in.toUpperCase 26 | } 27 | 28 | } 29 | val conditional = ConditionBlock[String]( 30 | (f: Future[String]) => f.map(e => e.length % 2 == 0), 31 | ActionBlock[String] { 32 | _.map { 33 | in => 34 | in + "[even]" 35 | } 36 | }, 37 | ActionBlock[String] { 38 | _.map { 39 | in => 40 | in + "[odd]" 41 | } 42 | } 43 | ) 44 | 45 | val initialIndex = 0 // can be started from any state in the diagram 46 | val initialInput = "Foo" // the input that can be saved for further processing 47 | 48 | val uml = new UML[String] ~> block1 ~> block2 ~> conditional 49 | val res = uml.from(initialIndex).iterate(initialInput) 50 | 51 | res map { r => r.content.get shouldBe "BLOCK1FOO[odd]" } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/scala/io/github/reugn/statecharts/uml/Block.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.statecharts.uml 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | 5 | /** 6 | * The trait that represents an abstract [[UML]] Block. 7 | * 8 | * @tparam T the type of the input/output objects. 9 | */ 10 | trait Block[T] extends (Future[T] => Future[T]) 11 | 12 | /** 13 | * A [[Block]] that represents a simple action [[UML]] unit that transforms 14 | * an input object using the transition function and returns the result. 15 | * 16 | * | 17 | * | Future[T] 18 | * | 19 | * ---------- 20 | * | action | 21 | * ---------- 22 | * | 23 | * | Future[T] 24 | * | 25 | * 26 | * @param action the block's transition function. 27 | * @tparam T the type of the input/output objects. 28 | */ 29 | case class ActionBlock[T](action: Future[T] => Future[T]) extends Block[T] { 30 | @throws(classOf[UMLException]) 31 | override def apply(data: Future[T]): Future[T] = action(data) 32 | } 33 | 34 | /** 35 | * A [[Block]] that represents a conditional [[UML]] unit which runs the condition 36 | * function first. And then, having the result, one of the specified Blocks 37 | * accordingly. 38 | * 39 | * | 40 | * true | false 41 | * -------<>------- 42 | * | | 43 | * --------- --------- 44 | * |t_block| |f_block| 45 | * --------- --------- 46 | * 47 | * @param cond the boolean condition function. 48 | * @param on_true the Block to execute on a positive condition. 49 | * @param on_false the Block to execute on a negative condition. 50 | * @param ec the implicit execution context. 51 | * @tparam T the type of the input/output objects. 52 | */ 53 | case class ConditionBlock[T](cond: Future[T] => Future[Boolean], on_true: Block[T], on_false: Block[T]) 54 | (implicit ec: ExecutionContext) extends Block[T] { 55 | @throws(classOf[UMLException]) 56 | override def apply(data: Future[T]): Future[T] = { 57 | def exec(b: Block[T]): Future[T] = b match { 58 | case a@ActionBlock(_) => 59 | a.apply(data) 60 | case cb@ConditionBlock(_, _, _) => 61 | cb.apply(data) 62 | } 63 | 64 | cond(data) flatMap { 65 | case true => exec(on_true) 66 | case false => exec(on_false) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/scala/io/github/reugn/statecharts/uml/UML.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.statecharts.uml 2 | 3 | import scala.collection.mutable.ListBuffer 4 | import scala.concurrent.{ExecutionContext, Future} 5 | import scala.language.higherKinds 6 | import scala.util.control.NonFatal 7 | 8 | case class UMLException(msg: String) extends Exception(msg) 9 | 10 | case class UMLResult[T](content: Option[T], lastStep: Int, error: Option[Throwable] = None) 11 | 12 | /** 13 | * An object that models a data flow as a UML Diagram. 14 | * 15 | * @param i the initial state index. 16 | * @tparam T the type of a UML [[Block]]s. 17 | */ 18 | class UML[T](private var i: Int = 0) extends Iterator[Block[T]] { 19 | self => 20 | 21 | protected val listBuffer: ListBuffer[Block[T]] = new ListBuffer[Block[T]]() 22 | protected var list: List[Block[T]] = _ 23 | 24 | protected var input: T = _ 25 | 26 | /** 27 | * Appends a building block to the UML. 28 | * 29 | * @param next the block to append. 30 | * @return [[self]] 31 | */ 32 | def ~>(next: Block[T]): UML[T] = { 33 | listBuffer += next 34 | self 35 | } 36 | 37 | /** 38 | * Builds the UML instance. 39 | * 40 | * @return [[self]] 41 | */ 42 | def ~>| : UML[T] = { 43 | list = listBuffer.toList 44 | self 45 | } 46 | 47 | /** 48 | * Tests whether this iterator can provide another element. 49 | * 50 | * @return `true` if a subsequent call to `next` will yield an element, 51 | * `false` otherwise. 52 | * @note Reuse: $preservesIterator 53 | */ 54 | override def hasNext: Boolean = { 55 | require(list != null) 56 | i < list.size 57 | } 58 | 59 | /** 60 | * Produces the next element of this iterator. 61 | * 62 | * @return the next element of this iterator, if `hasNext` is `true`, 63 | * undefined behavior otherwise. 64 | * @note Reuse: $preservesIterator 65 | */ 66 | override def next(): Block[T] = { 67 | require(list != null) 68 | val n = list(i) 69 | i += 1 70 | n 71 | } 72 | 73 | type Id[IN] = IN 74 | 75 | trait PF[F[_]] { 76 | def apply[PT](t: PT): F[PT] 77 | } 78 | 79 | object identity extends PF[Id] { 80 | def apply[PT](t: PT): PT = t 81 | } 82 | 83 | /** 84 | * Sets the diagram head index. 85 | * 86 | * @param ind the index to set. 87 | * @return [[self]] 88 | */ 89 | def from(ind: Int): UML[T] = { 90 | i = ind 91 | self 92 | } 93 | 94 | def getIndex: Int = i - 1 95 | 96 | /** 97 | * Iterates over the [[UML]] diagram and returns the processing result. 98 | * 99 | * @param initial the initial diagram input. 100 | * @param >> the result transformer; defaults to the [[UMLResult]] identity function. 101 | * @param ec the implicit execution context. 102 | * @tparam R the type of the higher kind return value. 103 | * @return a Future of the flow processing result. 104 | */ 105 | def iterate[R[_]](initial: T, >> : UMLResult[T] => R[T] = identity.apply[UMLResult[T]] _) 106 | (implicit ec: ExecutionContext): Future[R[T]] = { 107 | if (list == null) ~>| 108 | require(list.nonEmpty) 109 | 110 | def recursiveIter(block: Block[T], in: T): Future[R[T]] = { 111 | input = in 112 | block.apply(Future.successful(in)) flatMap { 113 | res => 114 | if (hasNext) { 115 | recursiveIter(next(), res) 116 | } else { 117 | Future.successful(>>(UMLResult[T](Some(res), getIndex))) 118 | } 119 | } 120 | } 121 | 122 | recursiveIter(next(), initial) recover { 123 | case e@UMLException(_) => 124 | >>(UMLResult[T](Some(input), getIndex, Some(e))) 125 | case NonFatal(e) => 126 | >>(UMLResult[T](Some(input), getIndex, Some(e))) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/scala/io/github/reugn/statecharts/fsm/FSM.scala: -------------------------------------------------------------------------------- 1 | package io.github.reugn.statecharts.fsm 2 | 3 | import java.util.concurrent.{Callable, Executors, TimeUnit} 4 | import scala.collection.mutable 5 | import scala.concurrent.duration.{Duration, _} 6 | import scala.concurrent.{ExecutionContext, Future} 7 | import scala.language.postfixOps 8 | import scala.util.control.NonFatal 9 | 10 | case class InvalidFSMTransition(msg: String, e: Throwable) extends Exception(msg, e) 11 | 12 | /** 13 | * An object that represents a Finite-state machine. 14 | * 15 | * @param currentState the initial state of the FSM. 16 | * @param graph the state diagram [[Map]] of the FSM. 17 | * @tparam S the type of the FSM state. 18 | * @tparam E the type of the FSM event. 19 | */ 20 | class FSM[S <: SideEffect, E] private[fsm](private var currentState: S, 21 | private val graph: Map[S, Map[E, S]]) { 22 | 23 | /** 24 | * The delayed transitions scheduler. 25 | */ 26 | private lazy val scheduler = Executors.newScheduledThreadPool(1) 27 | 28 | /** 29 | * Performs a transition for the given event. 30 | * 31 | * @param event the actual transition event. 32 | * @return the FSM state after the transition. 33 | */ 34 | @throws[InvalidFSMTransition]("on faulty transition.") 35 | def transition(event: E): S = this.synchronized { 36 | currentState.onExit() 37 | try { 38 | currentState = graph(currentState)(event) 39 | } catch { 40 | case NonFatal(e) => 41 | throw InvalidFSMTransition(s"Failed to transition from $currentState on $event", e) 42 | } 43 | currentState.onEnter() 44 | currentState 45 | } 46 | 47 | /** 48 | * Performs a delayed transition for the given event. 49 | * 50 | * @param event the actual transition event. 51 | * @param delay the delay [[Duration]]. 52 | * @param ec the implicit execution context. 53 | * @return the state after the transition. 54 | */ 55 | def delayedTransition(event: E, delay: Duration)(implicit ec: ExecutionContext): Future[S] = { 56 | val (t: Long, tu: TimeUnit) = durationToPair(delay) 57 | Future { 58 | scheduler.schedule((() => transition(event)): Callable[S], t, tu).get 59 | } 60 | } 61 | 62 | /** 63 | * Returns the current state. 64 | * 65 | * @return the current FSM state. 66 | */ 67 | def state(): S = this.synchronized { 68 | currentState 69 | } 70 | } 71 | 72 | object FSM { 73 | 74 | /** 75 | * Creates an [[FSM]] given the initial state and the list of the [[State]] objects. 76 | * 77 | * @param initial the initial state object of the FSM. 78 | * @param states the list of the [[State]] objects with transition rules. 79 | * @tparam S the type of the FSM state. 80 | * @tparam E the type of the FSM event. 81 | * @return a new FSM instance. 82 | */ 83 | def apply[S <: SideEffect, E](initial: S, states: State[S, E]*): FSM[S, E] = { 84 | val builder = new GraphBuilder[S, E].initialState(initial) 85 | states foreach builder.setState 86 | builder.build() 87 | } 88 | 89 | /** 90 | * A mutable builder for an [[FSM]]. 91 | * 92 | * @tparam S the type of the FSM state. 93 | * @tparam E the type of the FSM event. 94 | */ 95 | private[fsm] class GraphBuilder[S <: SideEffect, E] { 96 | self => 97 | 98 | private var initial: S = _ 99 | private val graph: mutable.Map[S, Map[E, S]] = mutable.Map() 100 | 101 | def initialState(state: S): GraphBuilder[S, E] = { 102 | initial = state 103 | self 104 | } 105 | 106 | def setState(state: State[S, E]): GraphBuilder[S, E] = { 107 | graph.put(state.state, state.trans.toMap) 108 | self 109 | } 110 | 111 | def build(): FSM[S, E] = { 112 | new FSM(initial, graph.toMap) 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * The [[FSM]] state model. Contains a state object and a list 119 | * of available transitions. 120 | * 121 | * @param state the actual FSM state. 122 | * @param transitions the list of available transitions. 123 | * @tparam S the type of the FSM state. 124 | * @tparam E the type of the FSM event. 125 | */ 126 | case class State[S <: SideEffect, E](state: S, transitions: On[S, E]*) { 127 | val trans: mutable.Map[E, S] = mutable.Map() 128 | transitions.foreach(on => trans.put(on.event, on.dest)) 129 | } 130 | 131 | /** 132 | * The [[FSM]] transition rule model. Contains an event and a resulting 133 | * destination state object. 134 | * 135 | * @param event the actual event that has been received. 136 | * @param dest the destination state for the given even. 137 | * @tparam S the type of the FSM state. 138 | * @tparam E the type of the FSM event. 139 | */ 140 | case class On[+S <: SideEffect, +E](event: E, dest: S) 141 | 142 | /** 143 | * The supertype for the [[FSM]] state to handle transition side effects. 144 | */ 145 | trait SideEffect { 146 | /** 147 | * A method that is called in [[FSM.transition]] before returning 148 | * the state. 149 | * Defaults to no-action. 150 | */ 151 | def onEnter(): Unit = {} 152 | 153 | /** 154 | * A method that is called in [[FSM.transition]] before transitioning 155 | * to a new state. 156 | * Defaults to no-action. 157 | */ 158 | def onExit(): Unit = {} 159 | } 160 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. --------------------------------------------------------------------------------