CS2113/T 2018
  • Flat (current format)
  •     Nested
  • Schedule
  • Textbook
  • Admin Info
  • Report Bugs
  • Slack
  • Forum
  • Instructors
  • IVLE Announcements
  • IVLE File Submissions
  • Tutorial Schedule
  • Team IDs
  • Java Coding Standard
  • samplerepo-things
  • Addressbook-level1
  • Addressbook-level2
  • Addressbook-level3
  • Addressbook-level4
  • Projects List
  • Week 7 [Oct 1]

    Todo

    Admin info to read:

    Admin Project: v1.1 [week 7]

    Overview: Update UG and DG in the repo, update project plan in repo, attempt to do global-impact changes to the code base.

    v1.1 Summary of Milestone

    Milestone Minimum acceptable performance to consider as 'reached'
    Team org/repo set up as stated
    Project plan formalized as stated
    Have done some enhancement to the code at least attempted to enhance the code of a component, even if you did not fully succeed
    All project documents updated as required updated docs are merged to the master branch

    Reminder: Reaching individual and team milestones are considered for grading the project management component of your project grade.

    A. Process:

    Evaluates: How well you did in project management related aspects of the project, as an individual and as a team

    Based on: Supervisor observations of project milestones and GitHub data.

    Milestones need to be reached the midnight before of the tutorial for it to be counted as achieved. To get a good grade this aspect, achieve recommended weekly progress in at least 6/10 weeks.

    Other criteria:

    • Good use of GitHub milestones
    • Good use of GitHub release mechanism
    • Good version control, based on the repo
    • Reasonable attempt to use the forking workflow
    • Good task definition, assignment and tracking, based on the issue tracker
    • Good use of buffers (opposite: everything at the last minute)
    • Project done iteratively and incrementally (opposite: doing most of the work in one big burst)

    B. Team-based tasks:

    Evaluates: How much you contributed to common team-based tasks

    Based on: peer evaluations and tutor observations

    Relevant: [Admin Project Scope → Examples of team tasks ]

    Here is a non-exhaustive list of team-tasks:

    1. Necessary general code enhancements e.g.,
      1. Work related to renaming the product
      2. Work related to changing the product icon
      3. Morphing the product into a different product
    2. Setting up the GitHub, Travis, AppVeyor, etc.,
    3. Maintaining the issue tracker
    4. Release management
    5. Updating user/developer docs that are not specific to a feature  e.g. documenting the target user profile
    6. Incorporating more useful tools/libraries/frameworks into the product or the project workflow (e.g. automate more aspects of the project workflow using a GitHub plugin)

    v1.1 Project Management

    • Fix any errors in org/repo set up  (e.g. wrong repo name).

    • When all changes intended for v1.1 is merged to the master branch, use git tag feature to tag the current version as v1.1

    • Start tracking your project schedule using GitHub issue tracker and milestones. More instructions coming soon.

    v1.1 Documentation

    • Update User Guide, Developer Guide, README, and About Us pages as described earlier.

      Submission: merge your changes to the master branch of your repo.

    v1.1 Product

    • Each member should try to add some enhancements that are in line with the vision for v2.0. Preferably, these should be global-impact enhancements, touching as many other components as possible. Refer to the AddressBook-Level4 Developer Guide has some guidance on how to implement a new feature end-to-end.

    Admin F: Handling teamwork issues

    If your team is facing difficulties due to differences in skill/motivation /availability among team members,

    • First, do not expect everyone to have the same skill/motivation level as you. It is fine if someone wants to do less and have low expectations from the module. That doesn't mean that person is a bad person. Everyone is entitled to have their own priorities.

    • Second, don't give up. It is unfortunate that your team ended up in this situation, but you can turn it into a good learning opportunity. You don't get an opportunity to save a sinking team every day 😃

    • Third, if you care about your grade and willing to work for it, you need to take initiative to turn the situation around or else the whole team is going to suffer. Don't hesitate to take charge if the situation calls for it. By doing so, you'll be doing a favor for your team. Be professional, kind, and courteous to the team members, but also be firm and assertive. It is your grade that is at stake. Don't worry about making a bad situation worse. You won't know until you try.

    • Finally, don't feel angry or 'wronged'. Teamwork problems are not uncommon in this module and we know how to grade so that you will not be penalized for others' low contribution. We can use Git to find exactly what others did. It's not your responsibility to get others to contribute.

    Given below are some suggestions you can adopt if the project work is not going smooth due to team issues. Note that the below measures can result in some team members doing more work than others and earning better project grades than others. It is still better than sinking the whole team together.

    • Redistribute the work: Stronger programmers in the team should take over the critical parts of the code.

    • Enforce stricter integration workflow: Appoint an integrator (typically, the strongest programmer). His/her job is to maintain the integrated version of the code. He/she should not accept any code that breaks the existing product or is not up to the acceptable quality standard. It is up to others to submit acceptable code to the integrator. Note that if the integrator rejected your code unreasonably, you can still earn marks for that code. You are allowed to submit such 'rejected' code for grading. They can earn marks based on the quality of the code.

    If you have very unreliable or totally disengaged team members :

    • Re-allocate to others any mission-critical work allocated to that person so that such team members cannot bring down the entire team.
    • However, do not leave out such team members from project communications. Always keep them in the loop so that they can contribute any time they wish to.
    • Furthermore, evaluate them sincerely and fairly during peer evaluations so that they do get the grade their work deserves, no more, no less.
    • Be courteous to such team members too. Some folks have genuine problems that prevent them from contributing more although they may not be able tell you the reasons. Just do your best for the project and assume everyone else is doing their best too, although their best may be lower than yours.

    Admin Appendix C (FAQs) → Why I’m not allowed to use my favorite tool/framework/language etc.?

    Why I’m not allowed to use my favorite tool/framework/language etc.?

    We have chosen a basic set of tools after considering ease of learning, availability, typical-ness, popularity, migration path to other tools, etc. There are many reasons for limiting your choices:

    Pedagogical reasons:

    • Sometimes 'good enough', not necessarily the best, tools are a better fit for beginners: Most bleeding edge, most specialized, or most sophisticated tools are not suitable for a beginner course. After mastering our toolset, you will find it easy to upgrade to such high-end tools by yourself. We do expect you to eventually (after this module) migrate to better tools and, having learned more than one tool, to attain a more general understanding about a family of tools.
    • We want you to learn to thrive under given conditions: As a professional Software Engineer, you must learn to be productive in any given tool environment, rather than insist on using your preferred tools. It is usually in small companies doing less important work that you get to chose your own toolset. Bigger companies working on mature products often impose some choices on developers, such as the project management tool, code repository, IDE, language etc. For example, Google used SVN as their revision control software until very recently, long after SVN fell out of popularity among developers. Sometimes this is due to cost reasons (tool licensing cost), and sometimes due to legacy reasons (because the tool is already entrenched in their code base).
      While programming in school is often a solo sport, programming in the industry is a team sport. As we are training you to become professional software engineers, it is important to get over the psychological hurdle of needing to satisfy individual preferences and get used to making the best of a given environment.

    Practical reasons:

    • Some of the LOs are tightly coupled to tools. Allowing more tools means tutors need to learn more tools, which increases their workload.
    • We provide learning resources for tools. e.g. 'Git guides'. Allowing more tools means we need to produce more resources.
    • When all students use the same tool, the collective expertise of the tool is more, increasing the opportunities for you to learn from each others.

    Meanwhile, feel free to share with peers your experience of using other tools.

    Outcomes

    Design

    W7.1 Can interpret basic sequence diagrams W7.1a Can explain/identify sequence diagrams

    Tools → UML → Sequence Diagrams →

    Introduction

    A UML sequence diagram captures the interactions between multiple objects for a given scenario.

    Consider the code below.

    class Machine {
    
        Unit producePrototype() {
            Unit prototype = new Unit();
            for (int i = 0; i < 5; i++) {
                prototype.stressTest();
            }
            return prototype;
        }
    }
    
    class Unit {
    
        public void stressTest() {
    
        }
    }
    
    

    Here is the sequence diagram to model the interactions for the method call producePrototype() on a Machine object.

    W7.1b Can interpret sequence diagrams with basic notation

    Tools → UML → Sequence Diagrams →

    Basic

    Notation:

    This sequence diagram shows some interactions between a human user and the Text UI of a CLI Minesweeper game.

    The player runs the newgame action on the TextUi object which results in the TextUi showing the minefield to the player. Then, the player runs the clear x y command; in response, the TextUi object shows the updated minefield.

    The :TextUi in the above example denotes an unnamed instance of the class TextUi. If there were two instances of TextUi in the diagram, they can be distinguished by naming them e.g. TextUi1:TextUi and TextUi2:TextUi.

    Arrows representing method calls should be solid arrows while those representing method returns should be dashed arrows.

    Note that unlike in object diagrams, the class/object name is not underlined in sequence diagrams.

    [Common notation error] Activation bar too long: The activation bar of a method cannot start before the method call arrives and a method cannot remain active after the method had returned.  In the two sequence diagrams below, the one on the left commits this error because the activation bar starts before the method Foo#xyz() is called and remains active after the method returns.

    [Common notation error] Broken activation bar: The activation bar should remain unbroken from the point the method is called until the method returns.  In the two sequence diagrams below, the one on the left commits this error because the activation bar for the method Foo#abc() is not contiguous, but appears as two pieces instead.

    W7.1c Can interpret sequence diagrams with loops

    Tools → UML → Sequence Diagrams →

    Loops

    Notation:

    The Player calls the mark x,y command or clear x y command repeatedly until the game is won or lost.

    W7.1d Can interpret sequence diagrams with object creation

    Tools → UML → Sequence Diagrams →

    Object Creation

    Notation:

    • The arrow that represents the constructor arrives at the side of the box representing the instance.
    • The activation bar represents the period the constructor is active.

    The Logic object creates a Minefield object.

    W7.1e Can interpret sequence diagrams with minimal notation

    Tools → UML → Sequence Diagrams →

    Minimal Notation

    To reduce clutter, activation bars and return arrows may be omitted if they do not result in ambiguities or loss of information. Informal operation descriptions such as those given in the example below can be used, if more precise details are not required for the task at hand.

    A minimal sequence diagram

    W7.1f Can draw basic sequence diagrams

    Design → Modelling → Modelling Behaviors

    Sequence Diagrams - Basic

    Explain in your own words the interactions illustrated by this Sequence Diagram:

    Consider the code below:

    class Person{
        Tag tag;
        String name;
    
        Person(String personName, String tagName){
            name = personName;
            tag = new Tag(tagName);
        }
    }
    

    class Tag{
        Tag(String value){
            //...
        }
    }
    
    class PersonList{
        void addPerson(Person p){
            //...
        }
    }
    

    Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:

    PersonList personList = new PersonList();
    while (hasRoom){
        Person p = new Person("Adam", "friend");
        personList.addPerson(p);
    }
    

    Find notation mistakes in the sequence diagram below:


    Evidence:

    Consider the code below:

    class Person{
        Tag tag;
        String name;
    
        Person(String personName, String tagName){
            name = personName;
            tag = new Tag(tagName);
        }
    }
    

    class Tag{
        Tag(String value){
            //...
        }
    }
    
    class PersonList{
        void addPerson(Person p){
            //...
        }
    }
    

    Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:

    PersonList personList = new PersonList();
    while (hasRoom){
        Person p = new Person("Adam", "friend");
        personList.addPerson(p);
    }
    

    Submission: Show during tutorial.

    W7.2 Can explain APIs W7.2a Can explain APIs

    Implementation → Reuse → APIs →

    What

    An Application Programming Interface (API) specifies the interface through which other programs can interact with a software component. It is a contract between the component and its clients.

    A class has an API (e.g., API of the Java String class, API of the Python str class) which is a collection of public methods that you can invoke to make use of the class.

    The GitHub API is a collection of Web request formats GitHub server accepts and the corresponding responses. We can write a program that interacts with GitHub through that API.

    When developing large systems, if you define the API of each components early, the development team can develop the components in parallel  because the future behavior of the other components are now more predictable.

    Choose the correct statements

    • a. A software component can have an API.
    • b. Any method of a class is part of its API.
    • c. Private methods of a class are not part of its API.
    • d. The API forms the contract between the component developer and the component user.
    • e. Sequence diagrams can be used to show how components interact with each other via APIs.

    (a) (c) (d) (e)

    Explanation: (b) is incorrect because private methods cannot be a part of the API

    Defining component APIs early is useful for developing components in parallel.

    True

    Explanation: Yes, once we know the precise behavior expected of each component, we can start developing them in parallel.


    Evidence:

    Know the API of the AddressBook component you are in charge of and the APIs of the other components your component depends on.

    W7.3 Can use intermediate-level sequence diagrams W7.3a Can interpret sequence diagrams with object deletion

    Tools → UML → Sequence Diagrams →

    Object Deletion

    UML uses an X at the end of the lifeline of an object to show it's deletion.

    💡 Although object deletion is not that important in languages such as Java that support automatic memory management, you can still show object deletion in UML diagrams to indicate the point at which the object ceases to be used.

    Notation:

    Note how the diagrams shows the deletion of the Minefield object

    W7.3b Can interpret sequence diagrams with self invocation

    Tools → UML → Sequence Diagrams →

    Self Invocation

    UML can show a method of an object calling another of its own methods.

    Notation:

    The markCellAt(...) method of a Logic object is calling its own updateState(...) method.

    In this variation, the Book#write() method is calling the Chapter#getText() method which in turn does a call back by calling the getAuthor() method of the calling object.

    W7.3c Can interpret sequence diagrams with alternative paths

    Tools → UML → Sequence Diagrams →

    Alternative Paths

    UML uses alt frames to indicate alternative paths.

    Notation:

    Minefield calls the Cell#setMine if the cell is supposed to be a mined cell, and calls the Cell:setMineCount(...) method otherwise.

    W7.3d Can interpret sequence diagrams with optional paths

    Tools → UML → Sequence Diagrams →

    Optional Paths

    UML uses opt frames to indicate optional paths.

    Notation:

    Logic#markCellAt(...) calls Timer#start() only if it is the first move of the player.

    W7.3e Can interpret sequence diagrams with reference frames

    Tools → UML → Sequence Diagrams →

    Reference Frames

    UML uses ref frame to allow a segment of the interaction to be omitted and shown as a separate sequence diagram. Reference frames help us to break complicated sequence diagrams into multiple parts or simply to omit details we are not interested in showing.

    Notation:

    The details of the get minefield appearance interactions have been omitted from the diagram.

    Those details are shown in a separate sequence diagram given below.

    W7.3f Can draw intermediate-level sequence diagrams

    Design → Modelling → Modelling Behaviors

    Sequence Diagrams - Intermediate

    What’s going on here?

    • a. Logic object is executing a parallel thread.
    • b. Logic object is executing a loop.
    • c. Logic object is creating another Logic instance.
    • d. One of Logic object’s methods is calling another of its methods.
    • e. Minefield object is calling a method of Logic.

    (d)

    Explain the interactions depicted in this sequence diagram.

    First, the createParser() method of an existing ParserFactory object is called. Then, ...

    Draw a sequence diagram to represent this code snippet.

    if (isFirstPage) {
        new Quote().print();
    }
    

    The Quote class:

    class Quote{
    
        String q;
    
        Quote(){
            q = generate();
        }
    
        String generate(){
            // ...
        }
    
        void print(){
            System.out.println(q);
        }
    
    }
    
    • Show new Quote().print(); as two method calls.
    • As the created Quote object is not assigned to a variable, it can be considered as 'deleted' soon after its print() method is called.

    Evidence:

    Explain the interactions depicted in this sequence diagram.

    First, the createParser() method of an existing ParserFactory object is called. Then, ...

    1. Explain the sequence diagrams given [AddressBook Level4: Developer Guide]
    2. Add more sequence diagrams to project documentation (to be done in future weeks)
    W7.3g Can interpret sequence diagrams with parallel paths

    Tools → UML → Sequence Diagrams →

    Parallel Paths

    UML uses par frames to indicate parallel paths.

    Notation:

    Logic is calling methods CloudServer#poll() and LocalServer#poll() in parallel.

    💡 If you show parallel paths in a sequence diagram, the corresponding Java implementation is likely to be multi-threaded   because a normal Java program cannot do multiple things at the same time.

    Implementation

    W7.4 Can use logging W7.4a Can explain logging

    Implementation → Error Handling → Logging →

    What

    Logging is the deliberate recording of certain information during a program execution for future reference. Logs are typically written to a log file but it is also possible to log information in other ways  e.g. into a database or a remote server.

    Logging can be useful for troubleshooting problems. A good logging system records some system information regularly. When bad things happen to a system  e.g. an unanticipated failure, their associated log files may provide indications of what went wrong and action can then be taken to prevent it from happening again.

    💡 A log file is like the black box of an airplane; they don't prevent problems but they can be helpful in understanding what went wrong after the fact.

    Why is logging like having the 'black box' in an airplane?

    (a)

    W7.4b Can use logging

    Implementation → Error Handling → Logging →

    How

    Most programming environments come with logging systems that allow sophisticated forms of logging. They have features such as the ability to enable and disable logging easily or to change the logging intensity.

    This sample Java code uses Java’s default logging mechanism.

    First, import the relevant Java package:

    import java.util.logging.*;
    

    Next, create a Logger:

    private static Logger logger = Logger.getLogger("Foo");
    

    Now, you can use the Logger object to log information. Note the use of logging level for each message. When running the code, the logging level can be set to WARNING so that log messages specified as INFO level (which is a lower level than WARNING) will not be written to the log file at all.

    // log a message at INFO level
    logger.log(Level.INFO, "going to start processing");
    //...
    processInput();
    if(error){
        //log a message at WARNING level
        logger.log(Level.WARNING, "processing error", ex);
    }
    //...
    logger.log(Level.INFO, "end of processing");
    

    Tutorials:

    • A video tutorial by SimplyCoded:

    Best Practices:


    Evidence:

    Use of logging in the code you have written in the module project or elsewhere.

    W7.5 Can use assertions W7.5a Can explain assertions

    Implementation → Error Handling → Assertions →

    What

    Assertions are used to define assumptions about the program state so that the runtime can verify them. An assertion failure indicates a possible bug in the code because the code has resulted in a program state that violates an assumption about how the code should behave.

    An assertion can be used to express something like when the execution comes to this point, the variable v cannot be null.

    If the runtime detects an assertion failure, it typically take some drastic action such as terminating the execution with an error message. This is because an assertion failure indicates a possible bug and the sooner the execution stops, the safer it is.

    In the Java code below, suppose we set an assertion that timeout returned by Config.getTimeout() is greater than 0. Now, if the Config.getTimeout() returned -1 in a specific execution of this line, the runtime can detect it as a assertion failure -- i.e. an assumption about the expected behavior of the code turned out to be wrong which could potentially be the result of a bug -- and take some drastic action such as terminating the execution.

    int timeout = Config.getTimeout(); 
    
    W7.5b Can use assertions

    Implementation → Error Handling → Assertions →

    How

    Use the assert keyword to define assertions.

    This assertion will fail with the message x should be 0 if x is not 0 at this point.

    x = getX();
    assert x == 0 : "x should be 0";
    ...
    

    Assertions can be disabled without modifying the code.

    java -enableassertions HelloWorld (or java -ea HelloWorld) will run HelloWorld with assertions enabled while java -disableassertions HelloWorld will run it without verifying assertions.

    Java disables assertions by default. This could create a situation where you think all assertions are being verified as true while in fact they are not being verified at all. Therefore, remember to enable assertions when you run the program if you want them to be in effect.

    💡 Enable assertions in Intellij (how?) and get an assertion to fail temporarily (e.g. insert an assert false into the code temporarily) to confirm assertions are being verified.

    Java assert vs JUnit assertions: They are similar in purpose but JUnit assertions are more powerful and customized for testing. In addition, JUnit assertions are not disabled by default. We recommend you use JUnit assertions in test code and Java assert in functional code.

    Tutorials:

    Best practices:


    Evidence:

    Explain assertions in AddressBook-Level4 code.

    W7.5c Can use assertions optimally

    Implementation → Error Handling → Assertions →

    When

    It is recommended that assertions be used liberally in the code. Their impact on performance is considered low and worth the additional safety they provide.

    Do not use assertions to do work because assertions can be disabled. If not, your program will stop working when assertions are not enabled.

    The code below will not invoke the writeFile() method when assertions are disabled.  If that method is performing some work that is necessary for your program, your program will not work correctly when assertions are disabled.

    ...
    assert writeFile() : "File writing is supposed to return true";
    

    Assertions are suitable for verifying assumptions about Internal Invariants, Control-Flow Invariants, Preconditions, Postconditions, and Class Invariants. Refer to [Programming with Assertions (second half)] to learn more.

    Exceptions and assertions are two complementary ways of handling errors in software but they serve different purposes. Therefore, both assertions and exceptions should be used in code.

    • The raising of an exception indicates an unusual condition created by the user  (e.g. user inputs an unacceptable input) or the environment  (e.g., a file needed for the program is missing).
    • An assertion failure indicates the programmer made a mistake in the code  (e.g., a null value is returned from a method that is not supposed to return null under any circumstances).

    A Calculator program crashes with an ‘assertion failure’ message when you try to find the square root of a negative number.

    (c)

    Explanation: An assertion failure indicates a bug in the code. (b) is not acceptable because of the word "terminated". The application should not fail at all for this input. But it could have used an exception to handle the situation internally.

    Which statements are correct?

    • a. Use assertions to indicate the programmer messed up; Use exceptions to indicate the user or the environment messed up.
    • b. Use exceptions to indicate the programmer messed up; Use assertions to indicate the user or the environment messed up.

    (a)


    Evidence:

    Give an example from the AddressBook-Level4 code where an exception is used and explain why an assertion is not suitable for that situation. Similarly, explain why an exception is not suitable for a place where AddressBook Level4 uses an assertion.

    W7.6 Can do exception handling in code W7.6a Can explain error handling

    Implementation → Error Handling → Introduction →

    What

    Well-written applications include error-handling code that allows them to recover gracefully from unexpected errors. When an error occurs, the application may need to request user intervention, or it may be able to recover on its own. In extreme cases, the application may log the user off or shut down the system. --Microsoft

    W7.6b Can explain exceptions

    Implementation → Error Handling → Exceptions →

    What

    Exceptions are used to deal with 'unusual' but not entirely unexpected situations that the program might encounter at run time.

    Exception:

    The term exception is shorthand for the phrase "exceptional event." An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions. –- Java Tutorial (Oracle Inc.)

    Examples:

    • A network connection encounters a timeout due to a slow server.
    • The code tries to read a file from the hard disk but the file is corrupted and cannot be read.
    W7.6c Can explain how exception handling is done typically

    Implementation → Error Handling → Exceptions →

    How

    Most languages allow code that encountered an "exceptional" situation to encapsulate details of the situation in an Exception object and throw/raise that object so that another piece of code can catch it and deal with it. This is especially useful when the code that encountered the unusual situation does not know how to deal with it.

    The extract below from the -- Java Tutorial (with slight adaptations) explains how exceptions are typically handled.

    When an error occurs at some point in the execution, the code being executed creates an exception object and hands it off to the runtime system. The exception object contains information about the error, including its type and the state of the program when the error occurred. Creating an exception object and handing it to the runtime system is called throwing an exception.

    After a method throws an exception, the runtime system attempts to find something to handle it in the call stack. The runtime system searches the call stack for a method that contains a block of code that can handle the exception. This block of code is called an exception handler. The search begins with the method in which the error occurred and proceeds through the call stack in the reverse order in which the methods were called. When an appropriate handler is found, the runtime system passes the exception to the handler. An exception handler is considered appropriate if the type of the exception object thrown matches the type that can be handled by the handler.

    The exception handler chosen is said to catch the exception. If the runtime system exhaustively searches all the methods on the call stack without finding an appropriate exception handler, the program terminates.

    Advantages of exception handling in this way:

    • The ability to propagate error information through the call stack.
    • The separation of code that deals with 'unusual' situations from the code that does the 'usual' work.

    Which are benefits of exceptions?

    • a. Exceptions allow us to separate normal code from error handling code.
    • b. Exceptions can prevent problems that happen in the environment.
    • c. Exceptions allow us to handle in one location an error raised in another location.

    (a) (c)

    Explanation: Exceptions cannot prevent problems in the environment. They can only be used to handle and recover from such problems.


    Evidence:

    Acceptable: Some code you wrote that involves exception handling.

    Suggested: Do the exercise in addressbook-level2: LO-Exceptions.

    Submission:

    • Options 1 (discouraged): Show the relevant code during the tutorial.
    • Options 2 (preferred): Create a PR against Addressbook-Level2 by following the instructions below.

    If you choose option 2, we recommend that you complete this week's Project Management LOs first; there are many ways to create PRs but we expect you to create PRs in a specific way, as specified in the LOs.

    W7.6d Can avoid using exceptions to control normal workflow

    Implementation → Error Handling → Exceptions →

    When

    In general, use exceptions only for 'unusual' conditions. Use normal return statements to pass control to the caller for conditions that are 'normal'.

    W7.7 Can use Java8 streams W7.7a Can use Java8 streams

    Tools → Java →

    Streams: Basic

    Java 8 introduced a number of new features (e.g. Lambdas, Streams) that are not trivial to learn but also extremely useful to know.

    Here is an overview of new Java 8 features . (written by Benjamin Winterberg)

    Tutorials:


    A video tutorial by well-known Java coach Venkat Subramaniam



    Evidence:

    1. Your code (can be toy examples) that use some Java 8 features.
    2. Explain some parts of [AddressBook - Level 4] code that use Java 8 features. e.g. AddressBook#syncMasterTagListWith(Person)
    W7.8 Can use JavaFX to build a simple GUI W7.8a Can use JavaFX to build a simple GUI

    Tools → Java →

    JavaFX: Basic

    Adapted (with permissions) from Marco Jakob's JavaFX 8 tutorial.

    JavaFx 9 Tutorial - Part 1: Scene Builder

    Introduction

    This tutorial will teach you how to create a new JavaFX application in IntelliJ, and to use the SceneBuilder to create a layouts for your application.

    Final Screenshot

    Prerequisites

    Do remember the installation path to SceneBuilder 8 as we will need it to configure IntelliJ in a later step.

    IntelliJ Configurations

    If this is the first time using IntelliJ, you need to tell IntelliJ where to find JDK 9 and SceneBuilder.

    Configuring JDK 9

    1. On the Welcome screen, press ConfigureProject DefaultProject Structure.
    Welcome Screen

    • If you already have a project open, go to the Welcome screen by going to FileClose Project.
    1. Under Project SDK:, press New...JDK.
    2. Select the directory that you install JDK on, and press OK.
    3. Under Project language level:, select 9 - Modules, private methods in interfaces etc..
    JDK 9 under Project Structure

    1. Press OK again.

    Configuring Scene Builder

    1. On the Welcome screen, press ConfigureSettings.
    2. On the left hand side, select Languages & FrameworksJavaFX
    3. Under Path to SceneBuilder:, select the path to where SceneBuilder is installed (e.g. C:\Users\Admin\AppData\Local\SceneBuilder\SceneBuilder.exe on Windows)
    Scene Builder settings

    The JavaDocs will come in handy when writing your own JavaFX applications:

    Additionally, Oracle also has a tutorial on JavaFX if you are interested.

    Create a new JavaFX Project

    1. On the Welcome screen, press Create New Project.
    • If you already have a project, you can create a new project by going FileNewProject....
    1. On the left side, select JavaFX. Make sure that the Project SDK is set to 9 and JavaFX Application is selected.
    New Project

    1. Press Next.
    2. Give a name for the application (e.g. AddressApp), and specify a suitable location.
    3. Press Finish. If prompted to create a new directory, just press OK.

    Remove the sample package and its content. We will manually create our own package and resources in our tutorial.

    We will also have to set up the IDE further, so that warnings and errors show up correctly when working with Java 9:

    1. Go to the menu FileProject Structure.
    2. Under Project language level:, ensure that 9 - Modules, private methods in interfaces etc. is selected.

    Create the Packages

    In We will create a package for each of the component. Ensure that your Project pane is open (Alt+1). Right click on the src folder, and select NewPackage:

    • seedu.address - contains the controller classes (i.e. the part that deals with the business logic)
    • seedu.address.model - contains the model classes (i.e. the part that deals with data)
    • seedu.address.view - contains the views (i.e. the part that deals with presenting the data to the user)
    New Packages

    In subsequent tutorials, our view package will also contain some controllers that are directly related to a single view. We will call them view-controllers.

    Create the FXML Layout File

    There are two ways to create the UI:

    • Use FXML, which is an XML format.
    • Programmatically create the interface in Java.

    We will use FXML for most parts, so that we can separate the view and controller from each other. Furthermore, we are able to use the Scene Builder tool to edit our FXML file. That means we will not have to directly work with XML.

    Right click on the view package, and press NewFXML file. For the file name, type PersonOverview.fxml.

    Design with Scene Builder

    Right-click on PersonOverview.fxml and choose Open with Scene Builder. Now you should see the Scene Builder with just an AnchorPane (visible under Hierarchy on the left).

    If IntelliJ prompts for a location of the SceneBuilder executable, make sure to point to where you install SceneBuilder.

    1. Select the Anchor Pane in your Hierarchy, and adjust the size under Layout (right side). (Pref Width: 600, Pref Height: 300)

    1. Add a Split Pane (horizontal) (under Containers) by dragging it from the Library into the main area. Right-click the Split Pane in the Hierarchy view and select Fit to Parent.

    1. Drag a TableView (under Controls in Library view) into the left side of the SplitPane. Select the TableView (not a Column) and set the following layout constraints in the Inspector to the TableView. Inside an AnchorPane you can always set anchors to the four borders (see this page for more information on Layouts).

    1. Go to the menu PreviewShow Preview in Window to see whether the layout configuration is done correctly. Try resizing the window. The TableView should resize together with the window as it is anchored to the borders.

    2. Change the column text (under Properties) to "First Name" and "Last Name".

    1. Select the TableView and choose constrainted-resize for the 'Column Resize Policy'. This ensures that the columns will always fill up the entire available space.

    1. Add a Label on the right side with the text "Person Details:". Adjust the layout using anchors (Top: 5, Left: 5. Right: Blank, Bottom: Blank).

    You can use the Search Bar at the top of Library/Inspector to find the respective controls/properties.

    1. Add a GridPane on the right side. Select it, and adjust its layout using anchors (Top: 30, Left: 5, Right: 5, Bottom: Blank).

    1. Add the following labels to the cells, such that the grid is of this form:
    First Name Label
    Last Name Label
    Street Label
    City Label
    Postal Code Label
    Birthday Label

    To add a row to the GridPane, select an existing row number, right click the row number and choose "Add Row Below".

    1. Add a ButtonBar at the bottom. Add three buttons to the bar ("New...", "Edit...","Delete"). Adjust the anchors so that it stays at the bottom right (Top: Blank, Left: Blank, Right: 10, Bottom: 5).

    1. Now you should see something like the following. Use the Preview menu to test its resizing behaviour.

    1. Save the .fxml file.

    Create the Main Application

    The PersonOverview.fxml that we just created only contains the content of our entire application. We need another FXML for our root layout, which will contain a menu bar and wraps PersonOverview.fxml.

    1. Inside IntelliJ, right click on the view package, and press NewFXML file. For the file name, type RootLayout.fxml.
    2. Right-click on RootLayout.fxml and choose Open with Scene Builder.
    3. Delete the AnchorPane. We will use another pane for our root layout.

    1. Add BorderPane by dragging it from the Library view into the main area.

    1. Resize the BorderPane (Pref Width: 600, Pref Height: 400)

    1. Add a MenuBar into the insert TOP slot. We will not implement menu functionality for now.

    The JavaFX Main Class

    Now, we need to create the main Java class that starts up our application with the RootLayout.fxml and adds the PersonOverview.fxml in the center.

    Right-click on your seedu.address package, and choose NewJavaFXApplication. For the class name, type MainApp.

    The generated MainApp.java class extends from Application and contains two methods. This is the basic structure that we need to start a JavaFX Application. The most important part for us is the start(Stage primaryStage) method. It is automatically called when the application is launch() from within the main() method.

    As you see, the start(...) method receives a Stage as parameter. The following graphic illustrates the structure of every JavaFX application:

    Image Source: http://www.oracle.com

    It's like a theater play: The Stage is the main container which is usually a Window with a border and the typical minimize, maximize and close buttons. Inside the Stage you add a Scene which can, of course, be switched out by another Scene. Inside the Scene the actual JavaFX nodes like AnchorPane, TextBox, etc. are added.

    See this page for more info on working with the JavaFX Scene Graph.


    Open MainApp.java and replace the code with the following:

    MainApp.java

    package seedu.address;
    
    import java.io.IOException;
    
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.scene.layout.AnchorPane;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    
    public class MainApp extends Application {
        private Stage primaryStage;
        private BorderPane rootLayout;
    
        @Override
        public void start(Stage primaryStage) {
            this.primaryStage = primaryStage;
            this.primaryStage.setTitle("AddressApp");
    
            initRootLayout();
    
            showPersonOverview();
        }
    
        /**
         * Initializes the root layout.
         */
        public void initRootLayout() {
            try {
                // Load root layout from fxml file.
                FXMLLoader loader = new FXMLLoader();
                loader.setLocation(MainApp.class.getResource("view/RootLayout.fxml"));
                rootLayout = loader.load();
    
                // Show the scene containing the root layout.
                Scene scene = new Scene(rootLayout);
                primaryStage.setScene(scene);
                primaryStage.show();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Shows the person overview inside the root layout.
         */
        public void showPersonOverview() {
            try {
                // Load person overview.
                FXMLLoader loader = new FXMLLoader();
                loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
                AnchorPane personOverview = loader.load();
    
                // Set person overview into the center of root layout.
                rootLayout.setCenter(personOverview);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Returns the main stage.
         */
        public Stage getPrimaryStage() {
            return primaryStage;
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    The various comments should give you some hints about what's going on.

    Importing JavaFx 9

    JavaFx 9 is a separate module from Java 9. We must ensure that the modules are imported correctly.

    Notice that IntellJ will complain about the imported modules "not being in the module graph":

    To fix this:

    1. Ensure that your Project pane is open (Alt+1). Right click on the src folder, and select Newmodule-info.java:
    2. Add the following requires and exports in order to import and export the modules correctly:
    module AddressApp {
        requires javafx.graphics;
        requires javafx.fxml;
        requires javafx.controls;
    
        exports seedu.address;
    }
    

    Running the Application

    If you run the application now (right click MainApp.java and select Run MainApp.main()), you should see something like this:

    Possible Issues

    If JavaFx fails to load PersonOverview.fxml, you might get the following error message:

    javafx.fxml.LoadException:
    /.../AddressApp/out/production/AddressApp/seedu/address/view/PersonOverview.fxml:15
    

    To solve this issue, open PersonOverview.fxml normally in IntelliJ and ensure that there is no such attribute around:

    fx:controller="seedu.address.view.PersonOverview"

    JavaFx 9 tutorial - Part 2: Model and TableView

    Introduction

    In this tutorial, we will create the other parts of the application (mainly the model and the controller). We will also use ObservableList and *Property to bind our list of new model's Persons, and their individual details, to the view, with the controller as the "middleman".

    Create the Model class

    We need a model class in order to hold information about the people in our address book. Add a new class to the model package (seedu.address.model) called Person. The Person class will encapsulate the details of an individual person such as name, address and birthday. Add the following code to the class. The JavaFX specifics will be explained after the code snippet.

    Person.java

    package seedu.address.model;
    
    import java.time.LocalDate;
    
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    
    /**
     * Model class for a Person.
     */
    public class Person {
    
        private final StringProperty firstName;
        private final StringProperty lastName;
        private final StringProperty street;
        private final IntegerProperty postalCode;
        private final StringProperty city;
        private final ObjectProperty< LocalDate > birthday;
    
        /**
         * Default constructor.
         */
        public Person() {
            this(null, null);
        }
    
        /**
         * Constructor with some initial data.
         */
        public Person(String firstName, String lastName) {
            this.firstName = new SimpleStringProperty(firstName);
            this.lastName = new SimpleStringProperty(lastName);
    
            // Some initial dummy data, just for convenient testing.
            this.street = new SimpleStringProperty("some street");
            this.postalCode = new SimpleIntegerProperty(1234);
            this.city = new SimpleStringProperty("some city");
            this.birthday = new SimpleObjectProperty< LocalDate >(LocalDate.of(1999, 2, 21));
        }
    
        public String getFirstName() {
            return firstName.get();
        }
    
        public void setFirstName(String firstName) {
            this.firstName.set(firstName);
        }
    
        public StringProperty firstNameProperty() {
            return firstName;
        }
    
        public String getLastName() {
            return lastName.get();
        }
    
        public void setLastName(String lastName) {
            this.lastName.set(lastName);
        }
    
        public StringProperty lastNameProperty() {
            return lastName;
        }
    
        public String getStreet() {
            return street.get();
        }
    
        public void setStreet(String street) {
            this.street.set(street);
        }
    
        public StringProperty streetProperty() {
            return street;
        }
    
        public int getPostalCode() {
            return postalCode.get();
        }
    
        public void setPostalCode(int postalCode) {
            this.postalCode.set(postalCode);
        }
    
        public IntegerProperty postalCodeProperty() {
            return postalCode;
        }
    
        public String getCity() {
            return city.get();
        }
    
        public void setCity(String city) {
            this.city.set(city);
        }
    
        public StringProperty cityProperty() {
            return city;
        }
    
        public LocalDate getBirthday() {
            return birthday.get();
        }
    
        public void setBirthday(LocalDate birthday) {
            this.birthday.set(birthday);
        }
    
        public ObjectProperty< LocalDate > birthdayProperty() {
            return birthday;
        }
    }
    

    Explanations

    • With JavaFX, it is common to use *Property for all fields of a model class. A Property allows us, for example, to automatically be notified when the lastName or any other variable is changed. This helps us keep the view in sync with the data.
    • To learn more about *Property, refer to Using JavaFX Properties and Binding
    • LocalDate, the type that we are using for birthday, is part of the new Date and Time API since JDK 8

    A List of Persons

    The main data that our application manages is simply a bunch of persons. Let's create a list of Person objects inside the MainApp class. All other controller classes will later get access to that central list inside the MainApp.

    ObservableList

    We are working with JavaFX view classes that need to be informed about any changes made to the list of persons. This is important, since otherwise the view would not be in sync with the data. For this purpose, JavaFX introduces some new Collection classes.

    Among all these collections, we need the ObservableList. To create a new ObservableList, add the following code at the beginning of the MainApp class. We'll also add a constructor that creates some sample data and a public getter method:

    MainApp.java

    // ... AFTER THE OTHER VARIABLES ...
    
    /**
    * The data as an observable list of {@link Person}.
    */
    private ObservableList< Person > personData = FXCollections.observableArrayList();
    
        /**
         * Constructor
         */
        public MainApp() {
            // Add some sample data
            personData.add(new Person("Hans", "Muster"));
            personData.add(new Person("Ruth", "Mueller"));
            personData.add(new Person("Heinz", "Kurz"));
            personData.add(new Person("Cornelia", "Meier"));
            personData.add(new Person("Werner", "Meyer"));
            personData.add(new Person("Lydia", "Kunz"));
            personData.add(new Person("Anna", "Best"));
            personData.add(new Person("Stefan", "Meier"));
            personData.add(new Person("Martin", "Mueller"));
        }
    
        /**
         * Returns the data as an observable list of {@link Person}.
         */
        public ObservableList< Person > getPersonData() {
            return personData;
        }
    
        // ... THE REST OF THE CLASS ...
    

    The PersonOverviewController

    We have our model and view. Let's get our data into our table. We'll need a controller for our PersonOverview.fxml to act as a "middleman" for the model and view.

    Create a normal Java class inside the view package called PersonOverviewController.

    Note: We must put the class inside the same package as PersonOverview.fxml, otherwise the SceneBuilder won't be able to find it.

    We will add some instance variables that give us access to the table and the labels inside the view. The fields and some methods have a special @FXML annotation. This is necessary in order for the .fxml file to have access to private fields and private methods. After we have everything set up in the .fxml file, the application will automatically fill the variables when the .fxml file is loaded.

    So let's add the following code:

    Note: Remember to always use the javafx imports, NOT awt or swing.

    PersonOverviewController.java

    package seedu.address.view;
    
    import javafx.fxml.FXML;
    import javafx.scene.control.Label;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import seedu.address.MainApp;
    import seedu.address.model.Person;
    
    public class PersonOverviewController {
        @FXML
        private TableView< Person> personTable;
        @FXML
        private TableColumn< Person, String> firstNameColumn;
        @FXML
        private TableColumn< Person, String> lastNameColumn;
    
        @FXML
        private Label firstNameLabel;
        @FXML
        private Label lastNameLabel;
        @FXML
        private Label streetLabel;
        @FXML
        private Label postalCodeLabel;
        @FXML
        private Label cityLabel;
        @FXML
        private Label birthdayLabel;
    
        // Reference to the main application.
        private MainApp mainApp;
    
        /**
         * The constructor. It is called before the initialize() method.
         */
        public PersonOverviewController() {
        }
    
        /**
         * Initializes the controller class. This method is automatically called
         * after the fxml file has been loaded.
         */
        @FXML
        private void initialize() {
            // Initialize the person table with the two columns.
            firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
            lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
        }
    
        /**
         * Is called by the main application to give a reference back to itself.
         */
        public void setMainApp(MainApp mainApp) {
            this.mainApp = mainApp;
    
            // Add observable list data to the table
            personTable.setItems(mainApp.getPersonData());
        }
    }
    

    Explanations

    • All fields and methods where the .fxml file needs access must be annotated with @FXML.
      • Actually, only if they are private, but it's better to have them private and mark them with the annotation!
    • The initialize() method is automatically called after the .fxml file has been loaded. At this time, all the FXML fields should have been initialized already.
    • The setCellValueFactory(...) that we set on the table colums are used to determine which field inside the Person objects should be used for the particular column. The arrow -> indicates that we're using a Java 8 feature called Lambdas. (Another option would be to use a PropertyValueFactory, but this is not type-safe).

    Note:

    We're only using StringProperty values for our table columns in this example. When you want to use IntegerProperty or DoubleProperty, the setCellValueFactory(...) must have an additional asObject():

    myIntegerColumn.setCellValueFactory(cellData ->
        cellData.getValue().myIntegerProperty().asObject());
    

    This is necessary because of a bad design decision of JavaFX (see this discussion for more details).

    Connecting MainApp with the PersonOverviewController

    The setMainApp(...) method must be called by the MainApp class. This gives us a way to access the MainApp object and get the list of Persons and other things. Add the following three lines to showPersonOverview() the method:

    MainApp.java - additional lines to add to showPersonOverview() method

    // (.... root layout statement goes here ....)
    
    // Give the controller access to the main app.
        PersonOverviewController controller = loader.getController();
        controller.setMainApp(this);
    
    // (.... catch statement goes here ....)
    

    Your showPersonOverview() method in MainApp should now look like this:

    MainApp.java - new showPersonOverview() method

    /**
    * Shows the person overview inside the root layout.
    */
    public void showPersonOverview() {
        try {
            // Load person overview.
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
            AnchorPane personOverview = loader.load();
    
            // Set person overview into the center of root layout.
            rootLayout.setCenter(personOverview);
    
            // Give the controller access to the main app.
            PersonOverviewController controller = loader.getController();
            controller.setMainApp(this);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    Hook the View to the controller

    We're almost there! But one thing is missing: We haven't told our PersonOverview.fxml file which controller to use, and which element should match to which field inside the controller.

    1. Open PersonOverview.fxml with the SceneBuilder.
    2. Open the Controller group on the left side (just below Hierarchy), and select the seedu.address.view.PersonOverviewController as the controller class.

    1. Select the TableView in the Hierarchy group.
    2. In the Inspector view, under the Code group, set 'fx:id' to personTable.

    1. Do the same for the table columns. Select firstNameColumn and lastNameColumn for the 'fx:id' respectively.
    2. For each label in the second column of the grid pane, choose the corresponding 'fx:id'.

    1. Save the .fxml file.

    Opening up the PersonOverviewController to JavaFx

    If you try and run the application now, you will encounter the following error:

    javafx.fxml.LoadException: ...
    
    ...
    
    Caused by: java.lang.IllegalAccessException: class javafx.fxml.FXMLLoader$ValueElement (in module javafx.fxml) cannot access class seedu.address.view.PersonOverviewController (in module AddressApp) because module AddressApp does not export seedu.address.view to module javafx.fxml
    

    This is because JavaFx is unable to access our PersonOverviewController class.

    To fix this, add this line of code to src/module-info.java:

    module AddressApp {
        ... 
    
        opens seedu.address.view;
    }
    

    The file should now look something like this:

    module-info.java

    module AddressApp {
        requires javafx.graphics;
        requires javafx.fxml;
        requires javafx.controls;
    
        exports seedu.address;
        opens seedu.address.view;
    }
    

    Start the Application

    When you start your application now, you should see something like this:

    Congratulations! The application now shows the list of Persons in the view!

    You may notice that selecting a person in the TableView does nothing to the labels at the right side. That is because the user interaction portion has not been programmed yet, which we will cover in the next part of the tutorial.

    After going through the two parts above, you should be familiar with building basic JavaFX GUIs using IntelliJ. You can continue with the original tutorial (which is written for Eclipse), with the following links:

    Evidence:

    Acceptable: Using JavaFX in any past project.

    Suggested: Do the exercise in [Addressbook-Level3: LO-JavaFX]

    Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.

    🅿️ Project

    W7.9 Can do small changes to an existing software

    Covered by the 'Product' component of v1.1:

    Tutorial 7

    An activity to do during tutorial:

    Consider the code below:

    class Person{
        Tag tag;
        String name;
    
        Person(String personName, String tagName){
            name = personName;
            tag = new Tag(tagName);
        }
    }
    

    class Tag{
        Tag(String value){
            //...
        }
    }
    
    class PersonList{
        void addPerson(Person p){
            //...
        }
    }
    

    Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:

    PersonList personList = new PersonList();
    while (hasRoom){
        Person p = new Person("Adam", "friend");
        personList.addPerson(p);
    }
    

    After that, draw a class diagram to match the code. Then, draw an object diagram to show the state of the objects after the code has executed one loop.

    Here are some sample diagrams to use as references:

    Questions to discuss during tutorial:

    Explain the interactions depicted in this sequence diagram.

    First, the createParser() method of an existing ParserFactory object is called. Then, ...


    For W7.1f Can draw basic sequence diagrams
    Details of the LO

    Design → Modelling → Modelling Behaviors

    Sequence Diagrams - Basic

    Explain in your own words the interactions illustrated by this Sequence Diagram:

    Consider the code below:

    class Person{
        Tag tag;
        String name;
    
        Person(String personName, String tagName){
            name = personName;
            tag = new Tag(tagName);
        }
    }
    

    class Tag{
        Tag(String value){
            //...
        }
    }
    
    class PersonList{
        void addPerson(Person p){
            //...
        }
    }
    

    Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:

    PersonList personList = new PersonList();
    while (hasRoom){
        Person p = new Person("Adam", "friend");
        personList.addPerson(p);
    }
    

    Find notation mistakes in the sequence diagram below:



    Evidence:

    Consider the code below:

    class Person{
        Tag tag;
        String name;
    
        Person(String personName, String tagName){
            name = personName;
            tag = new Tag(tagName);
        }
    }
    

    class Tag{
        Tag(String value){
            //...
        }
    }
    
    class PersonList{
        void addPerson(Person p){
            //...
        }
    }
    

    Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:

    PersonList personList = new PersonList();
    while (hasRoom){
        Person p = new Person("Adam", "friend");
        personList.addPerson(p);
    }
    

    Submission: Show during tutorial.

    For W7.2a Can explain APIs
    Details of the LO

    Implementation → Reuse → APIs →

    What

    An Application Programming Interface (API) specifies the interface through which other programs can interact with a software component. It is a contract between the component and its clients.

    A class has an API (e.g., API of the Java String class, API of the Python str class) which is a collection of public methods that you can invoke to make use of the class.

    The GitHub API is a collection of Web request formats GitHub server accepts and the corresponding responses. We can write a program that interacts with GitHub through that API.

    When developing large systems, if you define the API of each components early, the development team can develop the components in parallel  because the future behavior of the other components are now more predictable.

    Choose the correct statements

    • a. A software component can have an API.
    • b. Any method of a class is part of its API.
    • c. Private methods of a class are not part of its API.
    • d. The API forms the contract between the component developer and the component user.
    • e. Sequence diagrams can be used to show how components interact with each other via APIs.

    (a) (c) (d) (e)

    Explanation: (b) is incorrect because private methods cannot be a part of the API

    Defining component APIs early is useful for developing components in parallel.

    True

    Explanation: Yes, once we know the precise behavior expected of each component, we can start developing them in parallel.



    Evidence:

    Know the API of the AddressBook component you are in charge of and the APIs of the other components your component depends on.

    For W7.3f Can draw intermediate-level sequence diagrams
    Details of the LO

    Design → Modelling → Modelling Behaviors

    Sequence Diagrams - Intermediate

    What’s going on here?

    • a. Logic object is executing a parallel thread.
    • b. Logic object is executing a loop.
    • c. Logic object is creating another Logic instance.
    • d. One of Logic object’s methods is calling another of its methods.
    • e. Minefield object is calling a method of Logic.

    (d)

    Explain the interactions depicted in this sequence diagram.

    First, the createParser() method of an existing ParserFactory object is called. Then, ...

    Draw a sequence diagram to represent this code snippet.

    if (isFirstPage) {
        new Quote().print();
    }
    

    The Quote class:

    class Quote{
    
        String q;
    
        Quote(){
            q = generate();
        }
    
        String generate(){
            // ...
        }
    
        void print(){
            System.out.println(q);
        }
    
    }
    
    • Show new Quote().print(); as two method calls.
    • As the created Quote object is not assigned to a variable, it can be considered as 'deleted' soon after its print() method is called.


    Evidence:

    Explain the interactions depicted in this sequence diagram.

    First, the createParser() method of an existing ParserFactory object is called. Then, ...

    1. Explain the sequence diagrams given [AddressBook Level4: Developer Guide]
    2. Add more sequence diagrams to project documentation (to be done in future weeks)
    For W7.4b Can use logging
    Details of the LO

    Implementation → Error Handling → Logging →

    How

    Most programming environments come with logging systems that allow sophisticated forms of logging. They have features such as the ability to enable and disable logging easily or to change the logging intensity.

    This sample Java code uses Java’s default logging mechanism.

    First, import the relevant Java package:

    import java.util.logging.*;
    

    Next, create a Logger:

    private static Logger logger = Logger.getLogger("Foo");
    

    Now, you can use the Logger object to log information. Note the use of logging level for each message. When running the code, the logging level can be set to WARNING so that log messages specified as INFO level (which is a lower level than WARNING) will not be written to the log file at all.

    // log a message at INFO level
    logger.log(Level.INFO, "going to start processing");
    //...
    processInput();
    if(error){
        //log a message at WARNING level
        logger.log(Level.WARNING, "processing error", ex);
    }
    //...
    logger.log(Level.INFO, "end of processing");
    

    Tutorials:

    • A video tutorial by SimplyCoded:

    Best Practices:



    Evidence:

    Use of logging in the code you have written in the module project or elsewhere.

    For W7.5b Can use assertions
    Details of the LO

    Implementation → Error Handling → Assertions →

    How

    Use the assert keyword to define assertions.

    This assertion will fail with the message x should be 0 if x is not 0 at this point.

    x = getX();
    assert x == 0 : "x should be 0";
    ...
    

    Assertions can be disabled without modifying the code.

    java -enableassertions HelloWorld (or java -ea HelloWorld) will run HelloWorld with assertions enabled while java -disableassertions HelloWorld will run it without verifying assertions.

    Java disables assertions by default. This could create a situation where you think all assertions are being verified as true while in fact they are not being verified at all. Therefore, remember to enable assertions when you run the program if you want them to be in effect.

    💡 Enable assertions in Intellij (how?) and get an assertion to fail temporarily (e.g. insert an assert false into the code temporarily) to confirm assertions are being verified.

    Java assert vs JUnit assertions: They are similar in purpose but JUnit assertions are more powerful and customized for testing. In addition, JUnit assertions are not disabled by default. We recommend you use JUnit assertions in test code and Java assert in functional code.

    Tutorials:

    Best practices:



    Evidence:

    Explain assertions in AddressBook-Level4 code.

    For W7.5c Can use assertions optimally
    Details of the LO

    Implementation → Error Handling → Assertions →

    When

    It is recommended that assertions be used liberally in the code. Their impact on performance is considered low and worth the additional safety they provide.

    Do not use assertions to do work because assertions can be disabled. If not, your program will stop working when assertions are not enabled.

    The code below will not invoke the writeFile() method when assertions are disabled.  If that method is performing some work that is necessary for your program, your program will not work correctly when assertions are disabled.

    ...
    assert writeFile() : "File writing is supposed to return true";
    

    Assertions are suitable for verifying assumptions about Internal Invariants, Control-Flow Invariants, Preconditions, Postconditions, and Class Invariants. Refer to [Programming with Assertions (second half)] to learn more.

    Exceptions and assertions are two complementary ways of handling errors in software but they serve different purposes. Therefore, both assertions and exceptions should be used in code.

    • The raising of an exception indicates an unusual condition created by the user  (e.g. user inputs an unacceptable input) or the environment  (e.g., a file needed for the program is missing).
    • An assertion failure indicates the programmer made a mistake in the code  (e.g., a null value is returned from a method that is not supposed to return null under any circumstances).

    A Calculator program crashes with an ‘assertion failure’ message when you try to find the square root of a negative number.

    (c)

    Explanation: An assertion failure indicates a bug in the code. (b) is not acceptable because of the word "terminated". The application should not fail at all for this input. But it could have used an exception to handle the situation internally.

    Which statements are correct?

    • a. Use assertions to indicate the programmer messed up; Use exceptions to indicate the user or the environment messed up.
    • b. Use exceptions to indicate the programmer messed up; Use assertions to indicate the user or the environment messed up.

    (a)



    Evidence:

    Give an example from the AddressBook-Level4 code where an exception is used and explain why an assertion is not suitable for that situation. Similarly, explain why an exception is not suitable for a place where AddressBook Level4 uses an assertion.

    For W7.6c Can explain how exception handling is done typically
    Details of the LO

    Implementation → Error Handling → Exceptions →

    How

    Most languages allow code that encountered an "exceptional" situation to encapsulate details of the situation in an Exception object and throw/raise that object so that another piece of code can catch it and deal with it. This is especially useful when the code that encountered the unusual situation does not know how to deal with it.

    The extract below from the -- Java Tutorial (with slight adaptations) explains how exceptions are typically handled.

    When an error occurs at some point in the execution, the code being executed creates an exception object and hands it off to the runtime system. The exception object contains information about the error, including its type and the state of the program when the error occurred. Creating an exception object and handing it to the runtime system is called throwing an exception.

    After a method throws an exception, the runtime system attempts to find something to handle it in the call stack. The runtime system searches the call stack for a method that contains a block of code that can handle the exception. This block of code is called an exception handler. The search begins with the method in which the error occurred and proceeds through the call stack in the reverse order in which the methods were called. When an appropriate handler is found, the runtime system passes the exception to the handler. An exception handler is considered appropriate if the type of the exception object thrown matches the type that can be handled by the handler.

    The exception handler chosen is said to catch the exception. If the runtime system exhaustively searches all the methods on the call stack without finding an appropriate exception handler, the program terminates.

    Advantages of exception handling in this way:

    • The ability to propagate error information through the call stack.
    • The separation of code that deals with 'unusual' situations from the code that does the 'usual' work.

    Which are benefits of exceptions?

    • a. Exceptions allow us to separate normal code from error handling code.
    • b. Exceptions can prevent problems that happen in the environment.
    • c. Exceptions allow us to handle in one location an error raised in another location.

    (a) (c)

    Explanation: Exceptions cannot prevent problems in the environment. They can only be used to handle and recover from such problems.



    Evidence:

    Acceptable: Some code you wrote that involves exception handling.

    Suggested: Do the exercise in addressbook-level2: LO-Exceptions.

    Submission:

    • Options 1 (discouraged): Show the relevant code during the tutorial.
    • Options 2 (preferred): Create a PR against Addressbook-Level2 by following the instructions below.

    If you choose option 2, we recommend that you complete this week's Project Management LOs first; there are many ways to create PRs but we expect you to create PRs in a specific way, as specified in the LOs.

    For W7.7a Can use Java8 streams
    Details of the LO

    Tools → Java →

    Streams: Basic

    Java 8 introduced a number of new features (e.g. Lambdas, Streams) that are not trivial to learn but also extremely useful to know.

    Here is an overview of new Java 8 features . (written by Benjamin Winterberg)

    Tutorials:


    A video tutorial by well-known Java coach Venkat Subramaniam




    Evidence:

    1. Your code (can be toy examples) that use some Java 8 features.
    2. Explain some parts of [AddressBook - Level 4] code that use Java 8 features. e.g. AddressBook#syncMasterTagListWith(Person)
    For W7.8a Can use JavaFX to build a simple GUI
    Details of the LO

    Tools → Java →

    JavaFX: Basic

    Adapted (with permissions) from Marco Jakob's JavaFX 8 tutorial.

    JavaFx 9 Tutorial - Part 1: Scene Builder

    Introduction

    This tutorial will teach you how to create a new JavaFX application in IntelliJ, and to use the SceneBuilder to create a layouts for your application.

    Final Screenshot

    Prerequisites

    Do remember the installation path to SceneBuilder 8 as we will need it to configure IntelliJ in a later step.

    IntelliJ Configurations

    If this is the first time using IntelliJ, you need to tell IntelliJ where to find JDK 9 and SceneBuilder.

    Configuring JDK 9

    1. On the Welcome screen, press ConfigureProject DefaultProject Structure.
    Welcome Screen

    • If you already have a project open, go to the Welcome screen by going to FileClose Project.
    1. Under Project SDK:, press New...JDK.
    2. Select the directory that you install JDK on, and press OK.
    3. Under Project language level:, select 9 - Modules, private methods in interfaces etc..
    JDK 9 under Project Structure

    1. Press OK again.

    Configuring Scene Builder

    1. On the Welcome screen, press ConfigureSettings.
    2. On the left hand side, select Languages & FrameworksJavaFX
    3. Under Path to SceneBuilder:, select the path to where SceneBuilder is installed (e.g. C:\Users\Admin\AppData\Local\SceneBuilder\SceneBuilder.exe on Windows)
    Scene Builder settings

    The JavaDocs will come in handy when writing your own JavaFX applications:

    Additionally, Oracle also has a tutorial on JavaFX if you are interested.

    Create a new JavaFX Project

    1. On the Welcome screen, press Create New Project.
    • If you already have a project, you can create a new project by going FileNewProject....
    1. On the left side, select JavaFX. Make sure that the Project SDK is set to 9 and JavaFX Application is selected.
    New Project

    1. Press Next.
    2. Give a name for the application (e.g. AddressApp), and specify a suitable location.
    3. Press Finish. If prompted to create a new directory, just press OK.

    Remove the sample package and its content. We will manually create our own package and resources in our tutorial.

    We will also have to set up the IDE further, so that warnings and errors show up correctly when working with Java 9:

    1. Go to the menu FileProject Structure.
    2. Under Project language level:, ensure that 9 - Modules, private methods in interfaces etc. is selected.

    Create the Packages

    In We will create a package for each of the component. Ensure that your Project pane is open (Alt+1). Right click on the src folder, and select NewPackage:

    • seedu.address - contains the controller classes (i.e. the part that deals with the business logic)
    • seedu.address.model - contains the model classes (i.e. the part that deals with data)
    • seedu.address.view - contains the views (i.e. the part that deals with presenting the data to the user)
    New Packages

    In subsequent tutorials, our view package will also contain some controllers that are directly related to a single view. We will call them view-controllers.

    Create the FXML Layout File

    There are two ways to create the UI:

    • Use FXML, which is an XML format.
    • Programmatically create the interface in Java.

    We will use FXML for most parts, so that we can separate the view and controller from each other. Furthermore, we are able to use the Scene Builder tool to edit our FXML file. That means we will not have to directly work with XML.

    Right click on the view package, and press NewFXML file. For the file name, type PersonOverview.fxml.

    Design with Scene Builder

    Right-click on PersonOverview.fxml and choose Open with Scene Builder. Now you should see the Scene Builder with just an AnchorPane (visible under Hierarchy on the left).

    If IntelliJ prompts for a location of the SceneBuilder executable, make sure to point to where you install SceneBuilder.

    1. Select the Anchor Pane in your Hierarchy, and adjust the size under Layout (right side). (Pref Width: 600, Pref Height: 300)

    1. Add a Split Pane (horizontal) (under Containers) by dragging it from the Library into the main area. Right-click the Split Pane in the Hierarchy view and select Fit to Parent.

    1. Drag a TableView (under Controls in Library view) into the left side of the SplitPane. Select the TableView (not a Column) and set the following layout constraints in the Inspector to the TableView. Inside an AnchorPane you can always set anchors to the four borders (see this page for more information on Layouts).

    1. Go to the menu PreviewShow Preview in Window to see whether the layout configuration is done correctly. Try resizing the window. The TableView should resize together with the window as it is anchored to the borders.

    2. Change the column text (under Properties) to "First Name" and "Last Name".

    1. Select the TableView and choose constrainted-resize for the 'Column Resize Policy'. This ensures that the columns will always fill up the entire available space.

    1. Add a Label on the right side with the text "Person Details:". Adjust the layout using anchors (Top: 5, Left: 5. Right: Blank, Bottom: Blank).

    You can use the Search Bar at the top of Library/Inspector to find the respective controls/properties.

    1. Add a GridPane on the right side. Select it, and adjust its layout using anchors (Top: 30, Left: 5, Right: 5, Bottom: Blank).

    1. Add the following labels to the cells, such that the grid is of this form:
    First Name Label
    Last Name Label
    Street Label
    City Label
    Postal Code Label
    Birthday Label

    To add a row to the GridPane, select an existing row number, right click the row number and choose "Add Row Below".

    1. Add a ButtonBar at the bottom. Add three buttons to the bar ("New...", "Edit...","Delete"). Adjust the anchors so that it stays at the bottom right (Top: Blank, Left: Blank, Right: 10, Bottom: 5).

    1. Now you should see something like the following. Use the Preview menu to test its resizing behaviour.

    1. Save the .fxml file.

    Create the Main Application

    The PersonOverview.fxml that we just created only contains the content of our entire application. We need another FXML for our root layout, which will contain a menu bar and wraps PersonOverview.fxml.

    1. Inside IntelliJ, right click on the view package, and press NewFXML file. For the file name, type RootLayout.fxml.
    2. Right-click on RootLayout.fxml and choose Open with Scene Builder.
    3. Delete the AnchorPane. We will use another pane for our root layout.

    1. Add BorderPane by dragging it from the Library view into the main area.

    1. Resize the BorderPane (Pref Width: 600, Pref Height: 400)

    1. Add a MenuBar into the insert TOP slot. We will not implement menu functionality for now.

    The JavaFX Main Class

    Now, we need to create the main Java class that starts up our application with the RootLayout.fxml and adds the PersonOverview.fxml in the center.

    Right-click on your seedu.address package, and choose NewJavaFXApplication. For the class name, type MainApp.

    The generated MainApp.java class extends from Application and contains two methods. This is the basic structure that we need to start a JavaFX Application. The most important part for us is the start(Stage primaryStage) method. It is automatically called when the application is launch() from within the main() method.

    As you see, the start(...) method receives a Stage as parameter. The following graphic illustrates the structure of every JavaFX application:

    Image Source: http://www.oracle.com

    It's like a theater play: The Stage is the main container which is usually a Window with a border and the typical minimize, maximize and close buttons. Inside the Stage you add a Scene which can, of course, be switched out by another Scene. Inside the Scene the actual JavaFX nodes like AnchorPane, TextBox, etc. are added.

    See this page for more info on working with the JavaFX Scene Graph.


    Open MainApp.java and replace the code with the following:

    MainApp.java

    package seedu.address;
    
    import java.io.IOException;
    
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.scene.layout.AnchorPane;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    
    public class MainApp extends Application {
        private Stage primaryStage;
        private BorderPane rootLayout;
    
        @Override
        public void start(Stage primaryStage) {
            this.primaryStage = primaryStage;
            this.primaryStage.setTitle("AddressApp");
    
            initRootLayout();
    
            showPersonOverview();
        }
    
        /**
         * Initializes the root layout.
         */
        public void initRootLayout() {
            try {
                // Load root layout from fxml file.
                FXMLLoader loader = new FXMLLoader();
                loader.setLocation(MainApp.class.getResource("view/RootLayout.fxml"));
                rootLayout = loader.load();
    
                // Show the scene containing the root layout.
                Scene scene = new Scene(rootLayout);
                primaryStage.setScene(scene);
                primaryStage.show();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Shows the person overview inside the root layout.
         */
        public void showPersonOverview() {
            try {
                // Load person overview.
                FXMLLoader loader = new FXMLLoader();
                loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
                AnchorPane personOverview = loader.load();
    
                // Set person overview into the center of root layout.
                rootLayout.setCenter(personOverview);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Returns the main stage.
         */
        public Stage getPrimaryStage() {
            return primaryStage;
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    The various comments should give you some hints about what's going on.

    Importing JavaFx 9

    JavaFx 9 is a separate module from Java 9. We must ensure that the modules are imported correctly.

    Notice that IntellJ will complain about the imported modules "not being in the module graph":

    To fix this:

    1. Ensure that your Project pane is open (Alt+1). Right click on the src folder, and select Newmodule-info.java:
    2. Add the following requires and exports in order to import and export the modules correctly:
    module AddressApp {
        requires javafx.graphics;
        requires javafx.fxml;
        requires javafx.controls;
    
        exports seedu.address;
    }
    

    Running the Application

    If you run the application now (right click MainApp.java and select Run MainApp.main()), you should see something like this:

    Possible Issues

    If JavaFx fails to load PersonOverview.fxml, you might get the following error message:

    javafx.fxml.LoadException:
    /.../AddressApp/out/production/AddressApp/seedu/address/view/PersonOverview.fxml:15
    

    To solve this issue, open PersonOverview.fxml normally in IntelliJ and ensure that there is no such attribute around:

    fx:controller="seedu.address.view.PersonOverview"

    JavaFx 9 tutorial - Part 2: Model and TableView

    Introduction

    In this tutorial, we will create the other parts of the application (mainly the model and the controller). We will also use ObservableList and *Property to bind our list of new model's Persons, and their individual details, to the view, with the controller as the "middleman".

    Create the Model class

    We need a model class in order to hold information about the people in our address book. Add a new class to the model package (seedu.address.model) called Person. The Person class will encapsulate the details of an individual person such as name, address and birthday. Add the following code to the class. The JavaFX specifics will be explained after the code snippet.

    Person.java

    package seedu.address.model;
    
    import java.time.LocalDate;
    
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    
    /**
     * Model class for a Person.
     */
    public class Person {
    
        private final StringProperty firstName;
        private final StringProperty lastName;
        private final StringProperty street;
        private final IntegerProperty postalCode;
        private final StringProperty city;
        private final ObjectProperty< LocalDate > birthday;
    
        /**
         * Default constructor.
         */
        public Person() {
            this(null, null);
        }
    
        /**
         * Constructor with some initial data.
         */
        public Person(String firstName, String lastName) {
            this.firstName = new SimpleStringProperty(firstName);
            this.lastName = new SimpleStringProperty(lastName);
    
            // Some initial dummy data, just for convenient testing.
            this.street = new SimpleStringProperty("some street");
            this.postalCode = new SimpleIntegerProperty(1234);
            this.city = new SimpleStringProperty("some city");
            this.birthday = new SimpleObjectProperty< LocalDate >(LocalDate.of(1999, 2, 21));
        }
    
        public String getFirstName() {
            return firstName.get();
        }
    
        public void setFirstName(String firstName) {
            this.firstName.set(firstName);
        }
    
        public StringProperty firstNameProperty() {
            return firstName;
        }
    
        public String getLastName() {
            return lastName.get();
        }
    
        public void setLastName(String lastName) {
            this.lastName.set(lastName);
        }
    
        public StringProperty lastNameProperty() {
            return lastName;
        }
    
        public String getStreet() {
            return street.get();
        }
    
        public void setStreet(String street) {
            this.street.set(street);
        }
    
        public StringProperty streetProperty() {
            return street;
        }
    
        public int getPostalCode() {
            return postalCode.get();
        }
    
        public void setPostalCode(int postalCode) {
            this.postalCode.set(postalCode);
        }
    
        public IntegerProperty postalCodeProperty() {
            return postalCode;
        }
    
        public String getCity() {
            return city.get();
        }
    
        public void setCity(String city) {
            this.city.set(city);
        }
    
        public StringProperty cityProperty() {
            return city;
        }
    
        public LocalDate getBirthday() {
            return birthday.get();
        }
    
        public void setBirthday(LocalDate birthday) {
            this.birthday.set(birthday);
        }
    
        public ObjectProperty< LocalDate > birthdayProperty() {
            return birthday;
        }
    }
    

    Explanations

    • With JavaFX, it is common to use *Property for all fields of a model class. A Property allows us, for example, to automatically be notified when the lastName or any other variable is changed. This helps us keep the view in sync with the data.
    • To learn more about *Property, refer to Using JavaFX Properties and Binding
    • LocalDate, the type that we are using for birthday, is part of the new Date and Time API since JDK 8

    A List of Persons

    The main data that our application manages is simply a bunch of persons. Let's create a list of Person objects inside the MainApp class. All other controller classes will later get access to that central list inside the MainApp.

    ObservableList

    We are working with JavaFX view classes that need to be informed about any changes made to the list of persons. This is important, since otherwise the view would not be in sync with the data. For this purpose, JavaFX introduces some new Collection classes.

    Among all these collections, we need the ObservableList. To create a new ObservableList, add the following code at the beginning of the MainApp class. We'll also add a constructor that creates some sample data and a public getter method:

    MainApp.java

    // ... AFTER THE OTHER VARIABLES ...
    
    /**
    * The data as an observable list of {@link Person}.
    */
    private ObservableList< Person > personData = FXCollections.observableArrayList();
    
        /**
         * Constructor
         */
        public MainApp() {
            // Add some sample data
            personData.add(new Person("Hans", "Muster"));
            personData.add(new Person("Ruth", "Mueller"));
            personData.add(new Person("Heinz", "Kurz"));
            personData.add(new Person("Cornelia", "Meier"));
            personData.add(new Person("Werner", "Meyer"));
            personData.add(new Person("Lydia", "Kunz"));
            personData.add(new Person("Anna", "Best"));
            personData.add(new Person("Stefan", "Meier"));
            personData.add(new Person("Martin", "Mueller"));
        }
    
        /**
         * Returns the data as an observable list of {@link Person}.
         */
        public ObservableList< Person > getPersonData() {
            return personData;
        }
    
        // ... THE REST OF THE CLASS ...
    

    The PersonOverviewController

    We have our model and view. Let's get our data into our table. We'll need a controller for our PersonOverview.fxml to act as a "middleman" for the model and view.

    Create a normal Java class inside the view package called PersonOverviewController.

    Note: We must put the class inside the same package as PersonOverview.fxml, otherwise the SceneBuilder won't be able to find it.

    We will add some instance variables that give us access to the table and the labels inside the view. The fields and some methods have a special @FXML annotation. This is necessary in order for the .fxml file to have access to private fields and private methods. After we have everything set up in the .fxml file, the application will automatically fill the variables when the .fxml file is loaded.

    So let's add the following code:

    Note: Remember to always use the javafx imports, NOT awt or swing.

    PersonOverviewController.java

    package seedu.address.view;
    
    import javafx.fxml.FXML;
    import javafx.scene.control.Label;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import seedu.address.MainApp;
    import seedu.address.model.Person;
    
    public class PersonOverviewController {
        @FXML
        private TableView< Person> personTable;
        @FXML
        private TableColumn< Person, String> firstNameColumn;
        @FXML
        private TableColumn< Person, String> lastNameColumn;
    
        @FXML
        private Label firstNameLabel;
        @FXML
        private Label lastNameLabel;
        @FXML
        private Label streetLabel;
        @FXML
        private Label postalCodeLabel;
        @FXML
        private Label cityLabel;
        @FXML
        private Label birthdayLabel;
    
        // Reference to the main application.
        private MainApp mainApp;
    
        /**
         * The constructor. It is called before the initialize() method.
         */
        public PersonOverviewController() {
        }
    
        /**
         * Initializes the controller class. This method is automatically called
         * after the fxml file has been loaded.
         */
        @FXML
        private void initialize() {
            // Initialize the person table with the two columns.
            firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
            lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
        }
    
        /**
         * Is called by the main application to give a reference back to itself.
         */
        public void setMainApp(MainApp mainApp) {
            this.mainApp = mainApp;
    
            // Add observable list data to the table
            personTable.setItems(mainApp.getPersonData());
        }
    }
    

    Explanations

    • All fields and methods where the .fxml file needs access must be annotated with @FXML.
      • Actually, only if they are private, but it's better to have them private and mark them with the annotation!
    • The initialize() method is automatically called after the .fxml file has been loaded. At this time, all the FXML fields should have been initialized already.
    • The setCellValueFactory(...) that we set on the table colums are used to determine which field inside the Person objects should be used for the particular column. The arrow -> indicates that we're using a Java 8 feature called Lambdas. (Another option would be to use a PropertyValueFactory, but this is not type-safe).

    Note:

    We're only using StringProperty values for our table columns in this example. When you want to use IntegerProperty or DoubleProperty, the setCellValueFactory(...) must have an additional asObject():

    myIntegerColumn.setCellValueFactory(cellData ->
        cellData.getValue().myIntegerProperty().asObject());
    

    This is necessary because of a bad design decision of JavaFX (see this discussion for more details).

    Connecting MainApp with the PersonOverviewController

    The setMainApp(...) method must be called by the MainApp class. This gives us a way to access the MainApp object and get the list of Persons and other things. Add the following three lines to showPersonOverview() the method:

    MainApp.java - additional lines to add to showPersonOverview() method

    // (.... root layout statement goes here ....)
    
    // Give the controller access to the main app.
        PersonOverviewController controller = loader.getController();
        controller.setMainApp(this);
    
    // (.... catch statement goes here ....)
    

    Your showPersonOverview() method in MainApp should now look like this:

    MainApp.java - new showPersonOverview() method

    /**
    * Shows the person overview inside the root layout.
    */
    public void showPersonOverview() {
        try {
            // Load person overview.
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
            AnchorPane personOverview = loader.load();
    
            // Set person overview into the center of root layout.
            rootLayout.setCenter(personOverview);
    
            // Give the controller access to the main app.
            PersonOverviewController controller = loader.getController();
            controller.setMainApp(this);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    Hook the View to the controller

    We're almost there! But one thing is missing: We haven't told our PersonOverview.fxml file which controller to use, and which element should match to which field inside the controller.

    1. Open PersonOverview.fxml with the SceneBuilder.
    2. Open the Controller group on the left side (just below Hierarchy), and select the seedu.address.view.PersonOverviewController as the controller class.

    1. Select the TableView in the Hierarchy group.
    2. In the Inspector view, under the Code group, set 'fx:id' to personTable.

    1. Do the same for the table columns. Select firstNameColumn and lastNameColumn for the 'fx:id' respectively.
    2. For each label in the second column of the grid pane, choose the corresponding 'fx:id'.

    1. Save the .fxml file.

    Opening up the PersonOverviewController to JavaFx

    If you try and run the application now, you will encounter the following error:

    javafx.fxml.LoadException: ...
    
    ...
    
    Caused by: java.lang.IllegalAccessException: class javafx.fxml.FXMLLoader$ValueElement (in module javafx.fxml) cannot access class seedu.address.view.PersonOverviewController (in module AddressApp) because module AddressApp does not export seedu.address.view to module javafx.fxml
    

    This is because JavaFx is unable to access our PersonOverviewController class.

    To fix this, add this line of code to src/module-info.java:

    module AddressApp {
        ... 
    
        opens seedu.address.view;
    }
    

    The file should now look something like this:

    module-info.java

    module AddressApp {
        requires javafx.graphics;
        requires javafx.fxml;
        requires javafx.controls;
    
        exports seedu.address;
        opens seedu.address.view;
    }
    

    Start the Application

    When you start your application now, you should see something like this:

    Congratulations! The application now shows the list of Persons in the view!

    You may notice that selecting a person in the TableView does nothing to the labels at the right side. That is because the user interaction portion has not been programmed yet, which we will cover in the next part of the tutorial.

    After going through the two parts above, you should be familiar with building basic JavaFX GUIs using IntelliJ. You can continue with the original tutorial (which is written for Eclipse), with the following links:


    Evidence:

    Acceptable: Using JavaFX in any past project.

    Suggested: Do the exercise in [Addressbook-Level3: LO-JavaFX]

    Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.

    For W7.9 Can do small changes to an existing software

    Covered by the 'Product' component of v1.1:

    Lecture 7

    Slides: Uploaded on IVLE.