////////////////// NOTE TO STUDENTS ////////////////////////// // For the purposes of our course, it's not necessary // that you understand or even look at the code in this file. ////////////////////////////////////////////////////////////// package o1.football3.gui import scala.swing._ import scala.swing.event._ import o1.util.assignments.{InvalidSignature, arg, withStudentSolution} import o1.football3._ import o1.gui.layout._ import o1.gui.Dialog._ import o1.gui.O1AppDefaults import scala.util._ import scala.swing.ListView.IntervalMode._ /** The singleton object `FootballApp` represents a simple application that a programmer * can use to experiment with the package `o1.football2`. * * '''NOTE TO STUDENTS: In this course, you don't need to understand how this object works * or can be used, apart from the fact that you can use this file to start the program.''' */ object FootballApp extends SimpleSwingApplication with O1AppDefaults { def top = new MainFrame { this.title = "Football3 Test App" val content = new SeasonPanel(this) this.contents = content this.defaultButton = content.startButton this.resizable = false this.pack() this.centerOnScreen() class SeasonPanel(val owner: Window) extends EasyPanel { val seasonClass = new DynamicClass[AnyRef]("o1.football3.Season", Seq()) val season = Try(seasonClass.instantiate()).map(new EnhancedSeason(_)).recover { case missing: InvalidSignature => println(missing.message); throw missing } if (season.isFailure) { println("Season statistics and the list of matches will not be shown. Individual matches can still be tracked.") } val countStat, highestStat, latestStat = new Label val stats = new EasyPanel { placeNW(countStat, (0, 0), OneSlot, Slight, (0, 5, 3, 5)) placeNW(latestStat, (1, 0), OneSlot, Slight, (0, 5, 3, 5)) placeNW(highestStat, (1, 1), OneSlot, Slight, (0, 5, 3, 5)) } val matchList = new ListView[String] { border = Swing.EtchedBorder selection.intervalMode = Single } this.listenTo(matchList.selection) reactions += { case ListSelectionChanged(source, _, isAdjusting) => if (!isAdjusting) { for (season <- this.season; game <- season(source.selection.anchorIndex)) { this.matchView.game = game } } } val matchView = new MatchPanel(true) val startButton = new Button("New...") this.listenTo(startButton) reactions += { case ButtonClicked(source) => this.addMatch() } val playedLabel = new Label("Played:") val matchScroller = new ScrollPane(matchList) { this.preferredSize = new Dimension(80, this.preferredSize.height) } withStudentSolution(SeasonPanel.this) { this.updateStatus() } placeNW(playedLabel, (0, 0), OneSlot, Slight, (5, 5, 3, 5)) placeNW(matchScroller, (0, 1), OneSlot, FillVertical(1), (0, 5, 3, 5)) placeNW(startButton, (0, 2), OneSlot, Slight, (0, 5, 3, 5)) placeNW(matchView, (1, 0), TwoHigh, FillBoth(1, 1), (5, 5, 3, 5)) placeNW(stats, (1, 2), OneSlot, Slight, (5, 5, 3, 0)) def addMatch() = { for { home <- requestChoice("Home team:", ExampleLeague.Clubs, RelativeTo(this)) away <- requestChoice("Away team:", ExampleLeague.Clubs.filterNot( _ == home ), RelativeTo(this)) } { val dialog = new OngoingMatchDialog(this.owner, home, away) dialog.open() for (newMatch <- dialog.finishedMatch; season <- this.season) { withStudentSolution(SeasonPanel.this) { season.applyDynamic[Unit]("addResult")(classOf[Match] -> arg(newMatch)) // temporarily(?) rephrased because IJ complains unnecessarily about season.addResult[Unit](classOf[Match] -> arg(newMatch)) this.updateStatus() this.matchList.selectIndices(season.numberOfMatches[Int] - 1) } } } } private def updateStatus() = { for (season <- this.season) { val matchText = if (season.numberOfMatches[Int] == 1) "1 match" else (season.numberOfMatches[Int].toString + " matches") this.countStat.text = s"${matchText}." this.latestStat.text = s"Latest: ${season.latestMatch[Option[Match]].getOrElse("n/a")}" this.highestStat.text = s"Biggest win: ${season.biggestWin[Option[Match]].getOrElse("n/a")}" this.matchList.listData = season.matches.map( _.toShortString ) } } // dynamic wrapper around Season, with a couple of added convenience methods class EnhancedSeason(wrapped: AnyRef) extends DynamicObject(wrapped) { def apply(n: Int) = this.applyDynamic[Option[Match]]("matchNumber")(classOf[Int] -> arg(n)) // temporarily(?) rephrased because IJ complains unnecessarily about this.matchNumber[Option[Match]](classOf[Int] -> arg(n)) def matches = (0 until this.numberOfMatches[Int]).flatMap( this(_) ) } implicit class EnhancedMatch(val game: Match) { def toShortString = this.game.home.abbreviation + "-" + this.game.away.abbreviation } } } } // Workaround for a bug. To be refactored for 2022. import o1.util.assignments.{Argument, arg, InvalidSignature} import scala.util.{Try, Success, Failure} import scala.reflect.ClassTag import scala.language.dynamics import java.lang.reflect.InvocationTargetException import java.lang.reflect.Constructor private[gui] class DynamicClass[Supertype <: AnyRef](private val className: String, private val constructorParameterTypes: Seq[Class[_]]) { private val actualClass = Try(Class.forName(className).asInstanceOf[Class[Supertype]]) recoverWith { case noSuchClass: ClassNotFoundException => println("The class " + className + " was not available."); throw noSuchClass case invalidClass: ClassCastException => invalidClass.printStackTrace(); throw invalidClass } val isUsable = { val constructor: Try[Constructor[_]] = Try(Class.forName(className).asInstanceOf[Class[Supertype]].getConstructor(constructorParameterTypes: _*)) constructor.isSuccess } def instantiate(parameters: Argument*): Supertype = this.actualClass match { case Failure(_) => throw new InvalidSignature("The class " + this.className + " is not available and wasn't successfully instantiated.") case Success(actualClass) => try { val constructor = actualClass.getConstructor(this.constructorParameterTypes: _*) constructor.newInstance((parameters.map( _.value )): _*) } catch { case problemWithinImplementation: InvocationTargetException => println("The instantiation of class " + this.className + " failed to complete.") throw problemWithinImplementation case instantiationProblem: Exception => throw new InvalidSignature("The class " + this.className + " wasn't successfully instantiated.") } } } private[o1] class DynamicObject[StaticType](private val wrapped: StaticType) extends Dynamic { def applyDynamic[ResultType: ClassTag](methodName: String)(parameters: (Class[_], Argument)*): ResultType = { val returnValue = try { val method = this.wrapped.getClass.getMethod(methodName, parameters.map( _._1 ): _*) method.invoke(this.wrapped, parameters.map( _._2.value ): _*) } catch { case problemWithinImplementation: InvocationTargetException => println("A call to the method " + methodName + " failed to complete.") throw problemWithinImplementation case otherProblem: Exception => throw new InvalidSignature("The method or variable " + methodName + " was not successfully accessed.") } val boxings = Map[Class[_], Class[_]](classOf[Boolean] -> classOf[java.lang.Boolean], classOf[Int] -> classOf[java.lang.Integer], classOf[Double] -> classOf[java.lang.Double], classOf[Char] -> classOf[java.lang.Character], classOf[Short] -> classOf[java.lang.Short], classOf[Long] -> classOf[java.lang.Long], classOf[Byte] -> classOf[java.lang.Byte], classOf[Float] -> classOf[java.lang.Float]) val expectedClassTag = implicitly[ClassTag[ResultType]] val expectedClass = expectedClassTag.runtimeClass if (expectedClassTag == ClassTag(classOf[Unit]) || expectedClass.isInstance(returnValue) || boxings.get(expectedClass).exists( _.isInstance(returnValue) )) returnValue.asInstanceOf[ResultType] else throw new InvalidSignature("The return value of " + methodName + " was not of the expected type.") } def selectDynamic[ResultType](methodName: String)(implicit expectedClassTag: ClassTag[ResultType]): ResultType = { this.applyDynamic[ResultType](methodName)() } def get[StaticType] = this.wrapped }