Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
added instruction set
Browse files Browse the repository at this point in the history
includes basic instructions for wall, clock and hand and task control
includes clock selection with hands
includes basic task that holds a list of instructions
added some tests #2
moved validation to HandInteractionService
  • Loading branch information
V3lop5 committed Mar 7, 2021
1 parent 7fe40a5 commit 3d03781
Show file tree
Hide file tree
Showing 25 changed files with 722 additions and 6 deletions.
54 changes: 54 additions & 0 deletions src/instructionset/InstructionSet.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@file:Suppress("UNCHECKED_CAST")

package com.adclock.instructionset

import com.adclock.instructionset.clockselections.ClockSelection
import com.adclock.instructionset.clockselections.ClockSelectionParser
import com.adclock.instructionset.instructions.Instruction
import com.adclock.instructionset.instructions.InstructionParser
import com.adclock.util.findSubclasses
import kotlin.reflect.full.companionObjectInstance

object InstructionSet {

private val instructionParsers = findSubclasses(this::class.java.`package`.name, Instruction::class)
.associateWith { it.companionObjectInstance as? InstructionParser<*> }

private val selectionParsers = findSubclasses(this::class.java.`package`.name, ClockSelection::class)
.associateWith { it.companionObjectInstance as? ClockSelectionParser<*> }

fun deserialize(input: String): Instruction {
val instruction = input.take(3)
val parser = instructionParsers.values.find { instruction == it?.key }
?: throw IllegalArgumentException("No parser found for instruction $instruction.")

if (input.length > 3 && input[3] != ' ')
throw IllegalArgumentException("Blank after instruction expected.")

return parser.deserialize(input.drop(4))
}


fun serialize(instruction: Instruction): String {
val parser = instructionParsers[instruction::class]
?: throw IllegalArgumentException("No parser found for instruction type ${instruction::class.simpleName}.")
parser as InstructionParser<Instruction>
return "${parser.key.padEnd(3)} ${parser.serialize(instruction)}".trim()
}

fun deserializeSelection(selection: String) =
selection.split(",").map { split ->
selectionParsers.values.find { it?.deserializable(split) ?: false }?.deserialize(split)
?: throw IllegalArgumentException("No parser found for selection part $split")
}


fun serializeSelection(selection: ClockSelection): String {
val parser = selectionParsers[selection::class]
?: throw IllegalArgumentException("No parser found for instruction type ${selection::class.simpleName}.")
parser as ClockSelectionParser<ClockSelection>
return parser.serialize(selection)
}

fun serializeSelection(selections: List<ClockSelection>) = selections.joinToString(",") { serializeSelection(it) }
}
26 changes: 26 additions & 0 deletions src/instructionset/Task.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.adclock.instructionset

import com.adclock.instructionset.instructions.Instruction
import com.adclock.services.WallInteractionService

class Task(val instructions: List<Instruction>) {
private var current: Int = 0
var sleepUntil: Long = 0
internal set

fun isCompleted() = current >= instructions.size

fun apply(wallService: WallInteractionService) {
if (isCompleted())
throw IllegalStateException("This task is already completed. Can't perform more steps.")

if (sleepUntil > System.currentTimeMillis())
return

val instruction = instructions[current]
if (instruction.apply(this, wallService))
current++
}

internal fun restart() { current = 0 }
}
12 changes: 12 additions & 0 deletions src/instructionset/clockselections/ClockSelection.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.adclock.instructionset.clockselections

import com.adclock.model.Clock
import com.adclock.model.HandType


interface ClockSelection {
val hands: Array<HandType>

fun selected(clock: Clock): Boolean
}

25 changes: 25 additions & 0 deletions src/instructionset/clockselections/ClockSelectionParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.adclock.instructionset.clockselections

import com.adclock.model.HandType

interface ClockSelectionParser<T : ClockSelection> {
fun deserializable(string: String): Boolean

fun deserialize(hands: Array<HandType>, string: String): T
fun serializeParameters(selection: T): String

fun deserialize(string: String): T {
var clockPart = string
val hands = mutableListOf<HandType>()
while (clockPart.isNotBlank() && clockPart.last() in HandType.shortNames) {
hands += HandType.valueOfShort(clockPart.last())
clockPart = clockPart.dropLast(1)
}
return deserialize(hands.distinct().sorted().toTypedArray().ifEmpty { HandType.values() }, clockPart)
}

fun serialize(selection: T): String {
val hands = selection.hands.joinToString("") { it.short.toString() }
return serializeParameters(selection) + hands
}
}
25 changes: 25 additions & 0 deletions src/instructionset/clockselections/RangeClockSelection.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.adclock.instructionset.clockselections

import com.adclock.model.Clock
import com.adclock.model.HandType

class RangeClockSelection(
val range: IntRange,
override val hands: Array<HandType>
) : ClockSelection {
override fun selected(clock: Clock) = clock.id in range

companion object : ClockSelectionParser<RangeClockSelection> {
override fun deserializable(string: String) = "-" in string

override fun deserialize(hands: Array<HandType>, string: String): RangeClockSelection {
val from = string.substringBefore("-")
val to = string.substringAfter("-")
val range = from.toInt()..to.toInt()
require(!range.isEmpty()) { "Range ${range.first} to ${range.last} does not select any clocks." }
return RangeClockSelection(range, hands)
}

override fun serializeParameters(selection: RangeClockSelection) = "${selection.range.first}-${selection.range.last}"
}
}
20 changes: 20 additions & 0 deletions src/instructionset/clockselections/SingleClockSelection.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.adclock.instructionset.clockselections

import com.adclock.model.Clock
import com.adclock.model.HandType

class SingleClockSelection(
val id: Int,
override val hands: Array<HandType>
) : ClockSelection {
override fun selected(clock: Clock) = clock.id == id

companion object : ClockSelectionParser<SingleClockSelection> {
override fun deserializable(string: String) = "-" !in string

override fun deserialize(hands: Array<HandType>, string: String) =
SingleClockSelection(string.toInt(), hands)

override fun serializeParameters(selection: SingleClockSelection) = selection.id.toString()
}
}
8 changes: 8 additions & 0 deletions src/instructionset/instructions/Instruction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.adclock.instructionset.instructions

import com.adclock.instructionset.Task
import com.adclock.services.WallInteractionService

interface Instruction {
fun apply(task: Task, wallService: WallInteractionService): Boolean
}
9 changes: 9 additions & 0 deletions src/instructionset/instructions/InstructionParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.adclock.instructionset.instructions

interface InstructionParser<T : Instruction> {
val key: String

fun deserialize(input: String): T

fun serialize(instruction: T): String
}
28 changes: 28 additions & 0 deletions src/instructionset/instructions/basic/RepeatInstruction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.adclock.instructionset.instructions.basic

import com.adclock.instructionset.Task
import com.adclock.instructionset.instructions.Instruction
import com.adclock.instructionset.instructions.InstructionParser
import com.adclock.services.WallInteractionService

class RepeatInstruction : Instruction {

override fun apply(task: Task, wallService: WallInteractionService): Boolean {
task.restart()
return false
}


companion object : InstructionParser<RepeatInstruction> {
override val key: String
get() = "RPT"

override fun deserialize(input: String): RepeatInstruction {
require(input.isBlank()) { "No parameters for repeat instruction expected, but was $input." }
return RepeatInstruction()
}

override fun serialize(instruction: RepeatInstruction) = ""
}

}
27 changes: 27 additions & 0 deletions src/instructionset/instructions/basic/SleepInstruction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.adclock.instructionset.instructions.basic

import com.adclock.instructionset.Task
import com.adclock.instructionset.instructions.Instruction
import com.adclock.instructionset.instructions.InstructionParser
import com.adclock.services.WallInteractionService

class SleepInstruction(val sleep: Int) : Instruction {

override fun apply(task: Task, wallService: WallInteractionService): Boolean {
task.sleepUntil = System.currentTimeMillis() + sleep * 1000
return true
}

companion object : InstructionParser<SleepInstruction> {
override val key: String
get() = "SLP"

override fun deserialize(input: String): SleepInstruction {
require(input.isNotBlank()) { "Parameter sleep time (like 5) expected." }
return SleepInstruction(input.toInt())
}

override fun serialize(instruction: SleepInstruction) = instruction.sleep.toString()
}

}
23 changes: 23 additions & 0 deletions src/instructionset/instructions/clock/CalibrateInstruction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.adclock.instructionset.instructions.clock

import com.adclock.instructionset.clockselections.ClockSelection
import com.adclock.model.Clock
import com.adclock.services.ClockInteractionService

class CalibrateInstruction(selection: List<ClockSelection>) : ClockInstruction(selection) {
override fun apply(clockService: ClockInteractionService, clock: Clock) {
clockService.sendInit(clock)
}

companion object : ClockInstructionParser<CalibrateInstruction> {
override val key: String
get() = "CAL"

override fun deserialize(selection: List<ClockSelection>, parameters: String): CalibrateInstruction {
require(parameters.isBlank()) { "No parameters for calibrate instruction expected, but was $parameters." }
return CalibrateInstruction(selection)
}

override fun serializeParameters(instruction: CalibrateInstruction) = ""
}
}
18 changes: 18 additions & 0 deletions src/instructionset/instructions/clock/ClockInstruction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.adclock.instructionset.instructions.clock

import com.adclock.instructionset.clockselections.ClockSelection
import com.adclock.instructionset.instructions.wall.WallInstruction
import com.adclock.model.Clock
import com.adclock.services.ClockInteractionService
import com.adclock.services.WallInteractionService
import com.adclock.util.filter

abstract class ClockInstruction(internal val selection: List<ClockSelection>) : WallInstruction {
abstract fun apply(clockService: ClockInteractionService, clock: Clock)

override fun apply(wallService: WallInteractionService) {
wallService.wall.clocks.filter(selection).forEach {
apply(wallService.clockService, it)
}
}
}
21 changes: 21 additions & 0 deletions src/instructionset/instructions/clock/ClockInstructionParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.adclock.instructionset.instructions.clock

import com.adclock.instructionset.InstructionSet
import com.adclock.instructionset.clockselections.ClockSelection
import com.adclock.instructionset.instructions.InstructionParser

interface ClockInstructionParser<T : ClockInstruction> : InstructionParser<T> {

fun deserialize(selection: List<ClockSelection>, parameters: String): T
fun serializeParameters(instruction: T): String

override fun deserialize(input: String): T {
val selection = InstructionSet.deserializeSelection(input.substringBefore(' '))
return deserialize(selection, input.substringAfter(' ', ""))
}

override fun serialize(instruction: T): String {
return InstructionSet.serializeSelection(instruction.selection) + " " + serializeParameters(instruction)
}

}
25 changes: 25 additions & 0 deletions src/instructionset/instructions/hand/DirectionInstruction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.adclock.instructionset.instructions.hand

import com.adclock.instructionset.clockselections.ClockSelection
import com.adclock.instructionset.instructions.clock.ClockInstructionParser
import com.adclock.model.Direction
import com.adclock.model.Hand
import com.adclock.services.HandInteractionService

class DirectionInstruction(selection: List<ClockSelection>, val direction: Direction) : HandInstruction(selection) {
override fun apply(handService: HandInteractionService, hand: Hand) {
handService.setPlannedDirection(hand, direction)
}

companion object : ClockInstructionParser<DirectionInstruction> {
override val key: String
get() = "DIR"

override fun deserialize(selection: List<ClockSelection>, parameters: String): DirectionInstruction {
require(parameters.isNotBlank()) { "Parameter direction ${Direction.values().map { it.name }} expected." }
return DirectionInstruction(selection, Direction.valueOf(parameters))
}

override fun serializeParameters(instruction: DirectionInstruction) = instruction.direction.name
}
}
23 changes: 23 additions & 0 deletions src/instructionset/instructions/hand/HandInstruction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.adclock.instructionset.instructions.hand

import com.adclock.instructionset.clockselections.ClockSelection
import com.adclock.instructionset.instructions.clock.ClockInstruction
import com.adclock.model.Clock
import com.adclock.model.Hand
import com.adclock.model.HandType
import com.adclock.services.ClockInteractionService
import com.adclock.services.HandInteractionService

abstract class HandInstruction(selection: List<ClockSelection>) : ClockInstruction(selection) {
abstract fun apply(handService: HandInteractionService, hand: Hand)

override fun apply(clockService: ClockInteractionService, clock: Clock) {
val selectedHands = selection.find { it.selected(clock) }?.hands ?: emptyArray()

if (HandType.HOUR in selectedHands)
apply(clockService.handService, clock.hour)

if (HandType.MINUTE in selectedHands)
apply(clockService.handService, clock.minute)
}
}
Loading

0 comments on commit 3d03781

Please sign in to comment.