diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2d8ca6f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +certifi==2025.10.5 +charset-normalizer==3.4.4 +idna==3.11 +lxml==5.4.0 +Pygments==2.19.2 +PySnooper==1.2.3 +requests==2.32.5 +urllib3==2.5.0 +weblogin==1.11 diff --git a/src/Makefile b/src/Makefile index 3d1c7c9..70cb9f6 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ .PHONY: all -all: lpbook.pdf lpbook-teachers.pdf slides.pdf +all: lpbook.pdf lpbook-teachers.pdf slides-intro.pdf slides-tutorial-java.pdf LATEXFLAGS+= -shell-escape @@ -14,6 +14,7 @@ SRC+= test_introsort.py SRC+= tests_Makefile SRC+= cppjava.tex noweb.tex SRC+= gdb.tex +SRC+= factory.tex FIGS+= figs/pbr.png figs/pbrcover.jpg FIGS+= figs/jupyter-lab.png figs/jupyter-notebook.png @@ -26,6 +27,9 @@ SRC+= fracexample2.cpp DEPENDS+= FracExample.class SRC+= Fraction2.java +DEPENDS+= DocumentExample.class DocumentFactoryTest.class +SRC+= DocumentFactory2.java + SRC+= weblogin.tex SRC+= ../weblogin/doc/contents.tex @@ -38,14 +42,20 @@ fracexample2.cpp: cppjava.nw Fraction2.java: cppjava.nw ${NOTANGLE} +DocumentFactory2.java: factory.nw + ${NOTANGLE} + lpbook.pdf: lpbook.tex didactic.sty lpbook.pdf: ${SRC} ${FIGS} ${DEPENDS} lpbook-teachers.pdf: lpbook-teachers.tex didactic.sty lpbook-teachers.pdf: ${SRC} ${FIGS} ${DEPENDS} -slides.pdf: slides.tex didactic.sty -slides.pdf: ${SRC} ${FIGS} ${DEPENDS} +slides-intro.pdf: slides-intro.tex didactic.sty +slides-intro.pdf: ${SRC} ${FIGS} ${DEPENDS} + +slides-tutorial-java.pdf: slides-tutorial-java.tex didactic.sty +slides-tutorial-java.pdf: ${SRC} ${FIGS} ${DEPENDS} ${FIGS}:: ${MAKE} -C $(dir $@) $(notdir $@) @@ -66,7 +76,7 @@ test_introsort.py: introsort.nw .PHONY: clean clean: - ${RM} lpbook.pdf lpbook-teachers.pdf slides.pdf + ${RM} lpbook.pdf lpbook-teachers.pdf slides-intro.pdf slides-tutorial-java.pdf ${RM} merge.tex merge.sh ${RM} merge.aux merge.fdb_latexmk merge.fls merge.log ${RM} introsort.py introsort.tex @@ -82,7 +92,11 @@ clean: frac.mk: cppjava.nw ${NOTANGLE.mk} +factory.mk: factory.nw + ${NOTANGLE.mk} + include frac.mk +include factory.mk INCLUDE_MAKEFILES=../makefiles include ${INCLUDE_MAKEFILES}/tex.mk diff --git a/src/contents.tex b/src/contents.tex index 66dddad..675f275 100644 --- a/src/contents.tex +++ b/src/contents.tex @@ -36,6 +36,7 @@ } \mode
{\input{doctest.tex}} \mode
{\input{cppjava.tex}} +\mode
{\input{factory.tex}} \mode
{\input{noweb.tex}} \mode
{\input{weblogin.tex}} diff --git a/src/cppjava.nw b/src/cppjava.nw index d6c847d..dc24893 100644 --- a/src/cppjava.nw +++ b/src/cppjava.nw @@ -884,13 +884,21 @@ NOTANGLE.java=notangle -L'//line %L "%F"%N' -R$@ $< \ \begin{frame}[fragile] Now let's turn to the [[COMPILE.java]] variable. -As with other such variables, we'll use [[JAVAC]] and [[JAVACFLAGS]] to specify +As with other such variables, we'll use [[JAVAC]] and [[JAVACFLAGS]] to specify the compiler and its flags. We add the corresponding [[-L]] flag to [[noerr.pl]] in [[JAVACEXTRAS]]. + +However, there is one important detail we must consider: +[[javac]] writes error messages to stderr (as it should), but [[noerr.pl]] reads +from stdin via the pipe. +Since the pipe operator [[|]] only redirects stdout by default, we need to use +[[2>&1]] to redirect stderr to stdout. +This ensures that error messages flow through the pipe to [[noerr.pl]] for +proper line number translation. <>= JAVAC= javac JAVACFLAGS= -JAVACEXTRAS= | noerr.pl -L'//line %L \"%F\"%N' +JAVACEXTRAS= 2>&1 | noerr.pl -L'//line %L \"%F\"%N' COMPILE.java= ${JAVAC} ${JAVACFLAGS} $< ${JAVACEXTRAS} @ \end{frame} diff --git a/src/factory.nw b/src/factory.nw new file mode 100644 index 0000000..04a0e85 --- /dev/null +++ b/src/factory.nw @@ -0,0 +1,691 @@ +\chapter{Java, Design Patterns and Literate Programming} + +\mode* +\label{factory} + +\section{Introduction} + +\ltnote{% + We start with a try-first activity to activate prior knowledge about the + Factory Pattern. This follows variation theory's principle of starting with + students' existing understanding before introducing the formal concept. + This also helps us assess what students already know about design patterns. +}% +\begin{frame} +\begin{activity}\label{WhatIsFactory} + Have you heard of the Factory Pattern before? + What problem do you think it solves? + Think about it before continuing. +\end{activity} +\end{frame} + +In this tutorial, we'll learn how to write Java programs using literate +programming, specifically using the \noweb{} system. +We'll implement the Factory Pattern---a fundamental design pattern that +provides an interface for creating objects without specifying their exact +classes. + +\ltnote{% + We explain the purpose upfront: learning literate programming through a + design pattern example. This sets expectations and helps students understand + why we're combining these two topics. +}% + +We'll build a document management system where different types of documents +(PDF, Word, Text) can be created through a factory. +Along the way, you'll learn: +\begin{itemize} +\item How to organize Java code using literate programming +\item How to compile Java programs from \noweb{} sources using Make +\item How to handle Java compilation errors with line number translation +\item How the Factory Pattern promotes loose coupling and extensibility +\end{itemize} + +\begin{frame} +\begin{activity} +While reading this tutorial, try to write the code yourself and experiment +with it. +The best way to learn is by doing! +\end{activity} +\end{frame} + +\section{The Whole Picture} + +\ltnote{% + Following variation theory, we start with the whole before breaking it into + parts. Students see the complete, working example first, which provides + context for the detailed explanations that follow. + This also follows the literate programming principle of introducing concepts + in psychological order rather than compiler-required order. +}% + +Let's start by seeing how our document management system will be used. +Here's an example program that creates different types of documents: +\begin{frame}[fragile] +<>= +public class DocumentExample { + public static void main(String[] args) { + DocumentFactory factory = new DocumentFactory(); + + <> + <> + } +} +@ +\end{frame} + +\begin{frame}[fragile] +We use the factory to create different document types: +<>= +Document pdf = factory.createDocument("PDF"); +Document word = factory.createDocument("WORD"); +Document text = factory.createDocument("TEXT"); +@ +\end{frame} + +\begin{frame}[fragile] +Then we process each document polymorphically: +<>= +System.out.println("Processing documents:"); +pdf.open(); +pdf.save(); +pdf.close(); + +word.open(); +word.save(); +word.close(); + +text.open(); +text.save(); +text.close(); +@ +\end{frame} + +\ltnote{% + By showing the usage first, students can see the benefits of the Factory + Pattern: the client code doesn't need to know about concrete classes, just + the interface. This creates a contrast with the traditional approach of + using 'new' directly. +}% + +When we run this program, we see: +\begin{frame}[fragile] +\begin{pycode} +didactic_shell(["java", "DocumentExample"], minted_opts="numbers=none") +\end{pycode} +\end{frame} + +\begin{frame} +\begin{activity}\label{FactoryBenefit} + What advantage does using a factory provide compared to creating objects + with [[new PDFDocument()]], [[new WordDocument()]], etc. directly? + Think about what would happen if we needed to add a new document type. +\end{activity} +\end{frame} + +\ltnote{% + This try-first activity helps students discern the critical aspect of the + Factory Pattern: loose coupling. They should realize that with a factory, + adding new document types doesn't require changing client code. + This is the separation pattern: we vary the creation mechanism while keeping + the usage invariant. +}% + +\section{The Document Interface} + +Now let's understand how this works. +We start with an interface that defines what all documents must do: +\begin{frame}[fragile] +<>= +public interface Document { + void open(); + void save(); + void close(); +} +@ +\end{frame} + +\ltnote{% + We introduce the interface next to show the abstraction. This is the + invariant aspect: all documents share these operations. In the next + sections, we'll vary the implementations while keeping the interface + constant (generalization pattern). +}% + +The interface defines the contract that all document types must follow. +This allows us to treat different document types uniformly. + +\section{Concrete Document Implementations} + +Now we implement three concrete document types. +Each one provides its own behavior for opening, saving, and closing documents. + +\ltnote{% + Here we apply the generalization pattern: the same interface (invariant) + appears in different concrete implementations (variant). This helps students + discern that the interface defines the structure, while implementations + provide specific behaviors. +}% + +\subsection{PDF Documents} + +\begin{frame}[fragile] +<>= +public class PDFDocument implements Document { + @Override + public void open() { + System.out.println("Opening PDF document with Adobe Reader"); + } + + @Override + public void save() { + System.out.println("Saving PDF document"); + } + + @Override + public void close() { + System.out.println("Closing PDF document"); + } +} +@ +\end{frame} + +\subsection{Word Documents} + +\begin{frame}[fragile] +<>= +public class WordDocument implements Document { + @Override + public void open() { + System.out.println("Opening Word document with Microsoft Word"); + } + + @Override + public void save() { + System.out.println("Saving Word document"); + } + + @Override + public void close() { + System.out.println("Closing Word document"); + } +} +@ +\end{frame} + +\subsection{Text Documents} + +\begin{frame}[fragile] +<>= +public class TextDocument implements Document { + @Override + public void open() { + System.out.println("Opening text document with text editor"); + } + + @Override + public void save() { + System.out.println("Saving text document"); + } + + @Override + public void close() { + System.out.println("Closing text document"); + } +} +@ +\end{frame} + +\begin{frame} +\begin{activity}\label{AddNewType} + How would you add a new document type, say [[HTMLDocument]]? + What files would you need to modify? + Try to answer before continuing. +\end{activity} +\end{frame} + +\ltnote{% + This try-first activity reinforces the key benefit of the Factory Pattern. + Students should realize they need to: (1) create HTMLDocument.java + implementing Document, and (2) add a case in the factory. Importantly, they + don't need to modify client code like DocumentExample.java. + This creates contrast between the Factory Pattern and tight coupling. +}% + +\section{The Document Factory} + +The factory is where the magic happens. +It encapsulates the logic for creating the right type of document: + +\begin{frame}[fragile] +<>= +public class DocumentFactory { + public Document createDocument(String type) { + <> + } +} +@ +\end{frame} + +\begin{frame}[fragile] +<>= +switch (type.toUpperCase()) { + case "PDF": + return new PDFDocument(); + case "WORD": + return new WordDocument(); + case "TEXT": + return new TextDocument(); + default: + throw new IllegalArgumentException("Unknown document type: " + type); +} +@ +\end{frame} + +\ltnote{% + The factory centralizes object creation. This is the fusion pattern: + students must now see how the interface (abstraction), implementations + (concrete classes), and factory (creation logic) work together as a whole. + The critical aspect to discern is that the factory returns the interface + type, not concrete types, enabling polymorphism. +}% + +Notice that the factory method returns [[Document]], not a concrete type. +This is crucial: the client code only knows about the interface, not the +specific implementations. + +\section{Building with Make} + +\ltnote{% + Now we shift focus to the practical aspects of literate programming with + Java: how to build projects from \noweb{} sources. This is critical for + students to actually use literate programming in practice. +}% + +Now let's see how to build this system using Make. +We'll create a [[factory.mk]] file that contains the build rules. + +\begin{frame} +\begin{activity}\label{MakeExperience} + Are you familiar with Make and Makefiles? + If not, skim through chapter 2 of the GNU Make manual before continuing: + \url{https://www.gnu.org/software/make/manual/html_node/Introduction.html} +\end{activity} +\end{frame} + +\begin{frame}[fragile] +<>= +<> + +.PHONY: all +all: +<> + +<> + +.PHONY: clean +clean: clean-factory +clean-factory: + <> +@ +\end{frame} + +\subsection{Compiling Java Files} + +\ltnote{% + We introduce the pattern rule for Java compilation. This is a critical + aspect of using Make with Java. The pattern rule creates a reusable recipe + for any [[.class]] file from its corresponding [[.java]] file. +}% + +We need pattern rules to compile Java files and extract them from \noweb{} +sources: + +\begin{frame}[fragile] +<>= +%.class: %.java + ${COMPILE.java} +@ +\end{frame} + +\begin{frame}[fragile] +<>= +JAVAC= javac +JAVACFLAGS= -Xlint:all +JAVACEXTRAS= 2>&1 | noerr -L'//line %L \"%F\"%N' +COMPILE.java= ${JAVAC} ${JAVACFLAGS} $< ${JAVACEXTRAS} +@ +\end{frame} + +\ltnote{% + The [[JAVACEXTRAS]] variable is crucial: it pipes compiler output through + [[noerr]] to translate line numbers back to the \noweb{} source. This is + the Java equivalent of the [[-L]] flag for C++, but implemented as a + post-processing step since Java doesn't support [[#line]] directives. + This creates a contrast: different languages require different approaches + for line number translation. +}% + +\subsection{Dependencies Between Classes} + +Java classes often depend on other classes. +We need to specify these dependencies explicitly: + +\begin{frame}[fragile] +<>= +DocumentExample.class: Document.class DocumentFactory.class +DocumentFactory.class: Document.class +DocumentFactory.class: PDFDocument.class WordDocument.class TextDocument.class +PDFDocument.class: Document.class +WordDocument.class: Document.class +TextDocument.class: Document.class +@ +\end{frame} + +\ltnote{% + These dependencies ensure Make builds classes in the correct order. This is + important for students to understand: unlike C/C++ where headers declare + interfaces, Java requires the actual [[.class]] files to be present. + This is separation pattern: we isolate the dependency graph from the + compilation recipe. +}% + +\subsection{Adding to the Main Targets} + +\begin{frame}[fragile] +<>= +all: DocumentExample.class +@ +\end{frame} + +\begin{frame}[fragile] +<>= +rm -f Document.class +rm -f PDFDocument.class WordDocument.class TextDocument.class +rm -f DocumentFactory.class DocumentExample.class +@ +\end{frame} + +\section{Extracting Java Code from Noweb} + +\ltnote{% + This section focuses on tangling: extracting Java source files from the + \noweb{} document. The key challenge with Java is that it doesn't support + [[#line]] directives like C++, so we use comment-based line directives and + the [[noerr]] tool. +}% + +Now we need rules to extract the Java files from this \noweb{} source. +This is called "tangling" in literate programming terminology. + +\begin{frame}[fragile] +<>= +%.java: factory.nw + ${TANGLE.java} +@ +\end{frame} + +\begin{frame}[fragile] +<>= +TANGLE.java= notangle -L'//line %L \"%F\"%N' -R$@ $< \ + | cpif $@ \ + && noroots $< +@ +\end{frame} + +\ltnote{% + The [[-L]] flag with custom format creates comment-based line directives + that [[noerr]] can parse. The [[cpif]] utility only updates the file if + content changed (avoiding unnecessary recompilation). The [[noroots]] check + helps catch typos in chunk names. +}% + +Let's look at what [[notangle]] produces with the line directives: + +\begin{frame}[fragile] +\begin{activity}\label{TangleWithL} + What do you think the [[-L]] flag does? + Why would we want line directives in the generated Java files? +\end{activity} +\end{frame} + +\section{Line Number Translation in Java} + +\ltnote{% + This is a critical practical aspect. We create contrast between C++ (which + has built-in [[#line]] support) and Java (which requires external tools). + This helps students understand that literate programming tools must adapt to + different language ecosystems. +}% + +Unlike C++, Java doesn't support [[#line]] preprocessor directives. +So how do we get error messages that point to the correct line in our +[[.nw]] file? + +\begin{frame}[fragile] +Let's introduce an error to see how this works. +We'll create a version with a missing method: +<>= +public class DocumentFactory2 { + public Document createDocument(String type) { + return new PDFDocument(); + } + + public void brokenMethod() { + undefinedMethod(); + } +} +@ +\end{frame} + +\subsection{Without Line Directives} + +\begin{frame}[fragile] +First, let's tangle without the [[-L]] flag: +\begin{pycode} +didactic_shell("notangle -RDocumentFactory2.java factory.nw", + shell=True, lang="java") +\end{pycode} +\end{frame} + +If we compile this, we get: +\begin{frame}[fragile] +\begin{pycode} +didactic_shell("javac DocumentFactory2.java 2>&1", + shell=True, minted_opts="numbers=none") +\end{pycode} +\end{frame} + +\ltnote{% + The error points to [[DocumentFactory2.java]] line 7. But we want it to + point to our \noweb{} source! This motivates the need for line number + translation. +}% + +\subsection{With Line Directives and noerr} + +\begin{frame}[fragile] +Now let's tangle with line directives: +\begin{pycode} +didactic_shell("notangle -L'//line %L \"%F\"%N' -RDocumentFactory2.java factory.nw", + shell=True, lang="java") +\end{pycode} +\end{frame} + +Notice the [[//line]] comments! +These tell [[noerr]] how to map line numbers back to the source. + +\begin{frame}[fragile] +Now when we compile and filter through [[noerr]]: +\begin{pycode} +didactic_shell(""" +notangle -L'//line %L \"%F\"%N' -RDocumentFactory2.java factory.nw > DocumentFactory3.java; +javac DocumentFactory3.java 2>&1 | noerr -L'//line %L \"%F\"%N' + """, + shell=True, minted_opts="numbers=none") +\end{pycode} +\end{frame} + +\ltnote{% + Now the error correctly points to [[factory.nw]]! This is the payoff: + students can work with the literate source and still get meaningful error + messages. The contrast between with/without [[noerr]] helps them discern + why this tooling is essential. +}% + +\begin{frame} +\begin{activity}\label{TestErrors} + Try introducing your own errors in the code and observe how the error + messages change with and without the line directives. + This will help you understand the workflow. +\end{activity} +\end{frame} + +\section{Testing with JUnit} + +\ltnote{% + We add testing to show best practices in literate programming: tests should + be included in the literate source, close to the code they test. This makes + the connection between code and correctness explicit. +}% + +Good code includes tests. +Let's write JUnit tests for our factory: + +\begin{frame}[fragile] +<>= +import org.junit.Test; +import static org.junit.Assert.*; + +public class DocumentFactoryTest { + <> + <> +} +@ +\end{frame} + +\begin{frame}[fragile] +<>= +@Test +public void testCreatePDF() { + DocumentFactory factory = new DocumentFactory(); + Document doc = factory.createDocument("PDF"); + assertTrue(doc instanceof PDFDocument); +} + +@Test +public void testCreateWord() { + DocumentFactory factory = new DocumentFactory(); + Document doc = factory.createDocument("WORD"); + assertTrue(doc instanceof WordDocument); +} + +@Test +public void testCreateText() { + DocumentFactory factory = new DocumentFactory(); + Document doc = factory.createDocument("TEXT"); + assertTrue(doc instanceof TextDocument); +} +@ +\end{frame} + +\begin{frame}[fragile] +<>= +@Test(expected = IllegalArgumentException.class) +public void testUnknownType() { + DocumentFactory factory = new DocumentFactory(); + factory.createDocument("UNKNOWN"); +} +@ +\end{frame} + +\subsection{Building and Running Tests} + +\begin{frame}[fragile] +<>= +DocumentFactoryTest.class: DocumentFactory.class Document.class +DocumentFactoryTest.class: PDFDocument.class WordDocument.class TextDocument.class + +.PHONY: test-factory +test-factory: DocumentFactoryTest.class + java org.junit.runner.JUnitCore DocumentFactoryTest +@ +\end{frame} + +\begin{frame}[fragile] +<>= +all: DocumentFactoryTest.class +@ +\end{frame} + +\begin{frame} +\begin{activity}\label{RunTests} + Build and run the tests with [[make test-factory]]. + Then try modifying the factory to return the wrong type for one case and + see the test fail. +\end{activity} +\end{frame} + +\ltnote{% + This activity helps students understand the value of tests: they provide + quick feedback when changes break expected behavior. The try-first approach + (break it, see it fail) is more memorable than just seeing tests pass. +}% + +\section{The Complete Makefile} + +Here's the complete [[factory.mk]] file that ties everything together: + +\begin{frame}[fragile] +\inputminted{make}{factory.mk} +\end{frame} + +\section{Extending the Pattern} + +\ltnote{% + We end with generalization: showing how the same pattern applies to other + contexts. This helps students abstract the pattern beyond this specific + example. +}% + +\begin{frame} +\begin{activity}\label{OtherFactories} + Think of other situations where the Factory Pattern would be useful. + Consider: + \begin{itemize} + \item Database connections (MySQL, PostgreSQL, SQLite) + \item UI components (Button, TextField, Label for different platforms) + \item Parsers (JSON, XML, YAML) + \end{itemize} + What do these situations have in common? +\end{activity} +\end{frame} + +\ltnote{% + This final activity promotes generalization: varying the specific domain + while keeping the pattern invariant. Students should discern that the + Factory Pattern applies when you need to create objects from a family of + related types without coupling to concrete classes. +}% + +\section{Summary} + +You've learned: +\begin{enumerate} +\item How to write Java programs using literate programming with \noweb{} +\item How to organize code in psychological order, not compiler order +\item How to compile Java from \noweb{} sources using Make +\item How to translate error messages back to the source using [[noerr]] +\item How the Factory Pattern promotes loose coupling and extensibility +\item How to include tests in your literate programs +\end{enumerate} + +\begin{frame} +\begin{activity}\label{NextSteps} + Now try writing your own literate Java program implementing a different + design pattern, such as Strategy or Observer. + Use this tutorial as a template for your build system. +\end{activity} +\end{frame} + +\mode{\endinput} diff --git a/src/introsort.nw b/src/introsort.nw index 79fdd51..cfba580 100644 --- a/src/introsort.nw +++ b/src/introsort.nw @@ -100,8 +100,8 @@ Insertion Sort. <<[[sorted]] docstring>>= <> -This function uses the Intro Sort algorithm to sort the input list, instead of -MergeSort as the built-in `sorted` function. +This function uses the Intro Sort algorithm to sort the input list, +instead of TimSort as the built-in `sorted` function. @ \end{frame} @@ -1000,3 +1000,15 @@ endef All in all, the [[Makefile]] in the [[/tests]] directory looks like this. \inputminted{make}{tests_Makefile} + + +\section{Final reflection} + +\begin{frame}[fragile] + \begin{activity}[Final reflection] + Translate the program in this chapter into another language of your choice, + for example Java, C++, or Rust. + Try to do it without looking too much at how we did it in this chapter. + Compare when you're done. + \end{activity} +\end{frame} diff --git a/src/noweb.mk.nw b/src/noweb.mk.nw index 6f91657..a27e2e2 100644 --- a/src/noweb.mk.nw +++ b/src/noweb.mk.nw @@ -20,19 +20,21 @@ There is also a [[NOWEAVE.pdf]] to weave directly to PDF. This assumes that the file is independent, \ie no special LaTeX preamble. Consider the [[Makefile]] for this document as an example. -\inputminted[highlightlines={35-36,38-39,41-42,44-45,79-82,84-86}]{make}{Makefile} +% +\inputminted[highlightlines={35-39,41-48,56-65,82-85,87-89}]{make}{Makefile} +% We can see that we don't have to specify any recipes for [[lpbook.pdf]] and -[[slides.pdf]]. +[[slides.pdf]] (lines 41--48). That's because we have pattern rules for turning \TeX{} files in to PDF, -[[%.pdf: .tex]], in the [[tex.mk]] include file. +[[%.pdf: .tex]], in the [[tex.mk]] include file (included on line 88). We also see that for the files where the names don't match, for example -[[Fraction2.java: cppjava.nw]], we use a variable to specify the recipe, -[[NOWEAVE.tex]] and [[NOTANGLE]], respectively. -We get those \enquote{shortcuts} from the [[noweb.mk]] include file, which we -will look into next. +[[Fraction2.java: cppjava.nw]] (lines 38--39), we use a variable to specify the +recipe, [[NOWEAVE.tex]] and [[NOTANGLE]], respectively. +We get those \enquote{shortcuts} from the [[noweb.mk]] include file (included +on line 89), which we will look into next. -Finally, we can see at the very end of the [[Makefile]] that I keep the include -files in a separate directory, [[../makefiles]]. +Finally, we can see at the very end (lines 87--89) of the [[Makefile]] that I +keep the include files in a separate directory, [[../makefiles]]. In fact, I keep them in a separate repository~\autocite[See][]{makefiles}, which I include as a submodule in Git. This is because I reuse them in many projects and I want to keep them version @@ -40,13 +42,13 @@ managed and be able to update them easily---but keep a version if I don't need to update. -\section{Overview of the include file} +\section{Overview of the include file [[noweb.mk]]} The overall structure is the same as for other include files. We will cover the suffix rules for documentation first and then those for code. <>= -ifndef NOWEB_MK -NOWEB_MK = true +ifndef NOWEB_MK # Avoid multiple inclusion +NOWEB_MK=true <> diff --git a/src/slides.tex b/src/slides-intro.tex similarity index 100% rename from src/slides.tex rename to src/slides-intro.tex diff --git a/src/slides-tutorial-java.tex b/src/slides-tutorial-java.tex new file mode 100644 index 0000000..4e0c84d --- /dev/null +++ b/src/slides-tutorial-java.tex @@ -0,0 +1,12 @@ +\documentclass[9pt,ignoreframetext]{beamer} + +\let\chapter\part + +\input{preamble.tex} +\noweboptions{breakcode} + +\begin{document} +\mode +\input{factory.tex} +\mode* +\end{document} diff --git a/tutorial-java/.gitignore b/tutorial-java/.gitignore new file mode 100644 index 0000000..c3a4214 --- /dev/null +++ b/tutorial-java/.gitignore @@ -0,0 +1,10 @@ +Fraction.mk +Fraction.class +Fraction.java +Fraction.pdf +Fraction.tex +FractionTest.class +FractionTest.java +hamcrest-core-1.3.jar +junit-4.12.jar + diff --git a/tutorial-java/Fraction.nw b/tutorial-java/Fraction.nw new file mode 100644 index 0000000..a90fc08 --- /dev/null +++ b/tutorial-java/Fraction.nw @@ -0,0 +1,248 @@ +\title{En klass för bråk i Java} +\author{% + Daniel Bosk\thanks{% + Med viktiga bidrag från GitHub Copilot. + Denna fil är licensierad under MIT-licensen, se slutet. + } och prutt25-studenterna +} + +\maketitle +\tableofcontents + +\section{Introduktion} + +Vi vill kunna använda bråk precis som vi har heltal i Java. +För att göra detta skapar vi en klass som heter \texttt{Fraction}. +Denna klass kommer att representera ett bråk med en täljare och en nämnare. +Vi kommer också att implementera metoder för att utföra grundläggande +operationer med bråk, såsom addition, subtraktion, multiplikation och division. + +\subsection{Bygga och kompilera} + +Vi vill skriva en byggfil för att använda med GNU Make. +Vi har en huvudfil [[Makefile]] och vi vill inkludera en fil specifik för +klassen som heter [[Fraction.mk]]. + +Själva Java-filen tanglas från [[Fraction.nw]]. +När vi tanglar Java-koden från den litterära källkoden, så vill vi översätta +radnummer så att vi kan använda [[noerr.pl]] för att visa felmeddelanden med +korrekta radnummer. +<>= +Fraction.java: Fraction.nw + notangle -L'//line %L "%F"%N' -RFraction.java Fraction.nw > Fraction.java +@ + +Eftersom att klassen ska heta [[Fraction]], så behöver vi kompilera +[[Fraction.class]] från [[Fraction.java]]. +Eftersom vi har förberett för [[noerr.pl]], så vill vi även använda detta för +att visa felmeddelanden med korrekta radnummer. +Det vi behöver tänka på är att [[javac]] skriver felmeddelanden till stderr +(som sig bör), så vi behöver omdirigera detta till stdout för att kunna använda +[[noerr.pl]]. +<>= +Fraction.class: Fraction.java + javac Fraction.java 2>&1 | noerr.pl -L'//line %L "%F"%N' +@ + +Vi vill även lägga till [[Fraction.class]] som en beroende till [[all]], så att +den alltid byggs när vi kör [[make all]]. +<>= +all: Fraction.class +@ + +Vi vill även städa upp genom att ta bort de kompilerade klassfilerna +och allt annat som genererats. +<>= +clean: clean-Fraction +clean-Fraction: + rm -f Fraction.tex Fraction.aux Fraction.class Fraction.java Fraction.log + rm -f Fraction.mk Fraction.pdf Fraction.toc +@ + + +\section{Tester} + +Vi vill testa klassen [[Fraction]] med JUnit. +Vi skapar en fil som heter [[FractionTest.java]]. +<>= +all: FractionTest.class + +FractionTest.java: Fraction.nw + notangle -L'//line %L "%F"%N' -RFractionTest.java Fraction.nw > FractionTest.java + +clean: clean-FractionTest +clean-FractionTest: + rm -f FractionTest.class FractionTest.java +@ + +Denna fil kommer att innehålla tester för alla metoder i klassen [[Fraction]]. +Alla tester läggs till som metoder i klassen \texttt{FractionTest}. +<>= +import static org.junit.Assert.*; +import org.junit.Test; + +public class FractionTest { + <> +} +@ + +Vi lägger även till mål i byggfilen för att kompilera testerna. +För detta behöver vi [[junit-4.12.jar]] i klassvägen (class path, [[-cp]]). +Den å sin sida behöver [[hamcrest-core-1.3.jar]]. +<>= +FractionTest.class: FractionTest.java Fraction.class + javac -cp .:junit-4.12.jar:hamcrest-core-1.3.jar FractionTest.java 2>&1 \ + | noerr.pl -L'//line %L "%F"%N' +@ Vi har [[-cp .:junit-4.12.jar:hamcrest-core-1.3.jar]] för att inkludera +JUnit-biblioteket vid kompilering. +Detta kräver att du har JAR-filerna för JUnit i samma katalog som din byggfil. +Vi kan lägga till ytterligare ett mål för att ladda hem den: +<>= +junit-4.12.jar: + wget https://search.maven.org/remotecontent?filepath=junit/junit/4.12/junit-4.12.jar \ + -O junit-4.12.jar + +hamcrest-core-1.3.jar: + wget https://search.maven.org/remotecontent?filepath=org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar \ + -O hamcrest-core-1.3.jar +@ Sedan kan vi lägga till dem som beroenden för kompilering: +<>= +FractionTest.class: junit-4.12.jar hamcrest-core-1.3.jar +@ + +Vi vill även städa bort dessa. +Men då vi laddar hem dem är det snällare mot servrarna om vi inte städar bort +dem i onödan varje gång. +Så vi skapar ett separat mål för att städa bort dem, som vi explicit måste +köra. +<>= +clean-jars: + rm -f junit-4.12.jar hamcrest-core-1.3.jar +@ + +Vi lägger även till ett mål för att köra testerna. +<>= +test: FractionTest.class junit-4.12.jar hamcrest-core-1.3.jar + java -cp .:junit-4.12.jar:hamcrest-core-1.3.jar \ + org.junit.runner.JUnitCore FractionTest +@ Detta mål kör JUnit-testklassen och rapporterar resultaten i terminalen. + + +\section{Klassen Fraction} + +Filen [[Fraction.java]] innehåller definitionen av klassen \texttt{Fraction}. +Översiktligt ser den ut så här: +<>= +public class Fraction { + <> + <> +} +@ + +Vi vill testa först och implementera sedan. +I sann testdriven anda. + + +\section{Konstruktor} + +Vi skapar en konstruktor för klassen \texttt{Fraction} som initierar alla +attribut. +Vi vill kunna skapa ett bråk genom att ange täljare och nämnare. +Vi vill även kunna skapa ett bråk från en sträng som representerar bråket. + +Vi vill att klassen \texttt{Fraction} ska ha två attribut: \texttt{numerator} +och \texttt{denominator}. +Dessa attribut ska vara privata för att skydda dem från direkt åtkomst +utanför klassen. +Vi skapar också en konstruktor för att initiera dessa attribut. +<>= +private int numerator; +private int denominator; +@ + +Testerna först, vi vill kunna skapa objekt på följande sätt: +<>= +@@Test +public void testConstructorWithIntegers() { + Fraction frac = new Fraction(3, 4); +} + +@@Test +public void testConstructorWithString() { + Fraction frac = new Fraction("5/6"); +} +@ + +Konstruktorerna sätter bara attributen: +<>= +public Fraction(int numerator, int denominator) { + this.numerator = numerator; + this.denominator = denominator; +} + +public Fraction(String fraction) { + String[] parts = fraction.split("/"); + this.numerator = Integer.parseInt(parts[0]); + this.denominator = Integer.parseInt(parts[1]); +} +@ + + +\section{Getters för attributen} + +Vi vill ha getters för attributen. +Vi vill däremot inte ha några setters, eftersom att bråk är oföränderliga +objekt. +Vill vi ha en annan täljare, då skapar vi ett nytt bråk. +<>= +@@Test +public void testGetNumerator() { + Fraction frac = new Fraction(3, 4); + assertEquals(3, frac.getNumerator()); +} + +@@Test +public void testGetDenominator() { + Fraction frac = new Fraction(3, 4); + assertEquals(4, frac.getDenominator()); +} +@ + +Vi kan implementera våra getters genom att bara returnera attributen. +Vi behöver inte skapa någon kopia då attributen är av primitiv typ (int). +<>= +public int getNumerator() { + return this.numerator; +} + +public int getDenominator() { + return this.denominator; +} +@ + + +\section*{Licens} + +Denna fil är licensierad under MIT-licensen: +\begin{quote} +MIT License + +Copyright (c) 2025 Daniel Bosk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +\end{quote} diff --git a/tutorial-java/Makefile b/tutorial-java/Makefile new file mode 100644 index 0000000..b56f594 --- /dev/null +++ b/tutorial-java/Makefile @@ -0,0 +1,18 @@ +TARGETS= Fraction.pdf Fraction.mk + +all: ${TARGETS} + +Fraction.pdf: Fraction.tex + pdflatex Fraction.tex + pdflatex Fraction.tex + +Fraction.tex: Fraction.nw + noweave -latex Fraction.nw > Fraction.tex + +Fraction.mk: Fraction.nw + notangle -t2 -RFraction.mk Fraction.nw | sed "s/^ /\t/" > Fraction.mk + +clean: + rm ${TARGETS} + +include Fraction.mk diff --git a/weblogin b/weblogin index d45baa7..e2b718f 160000 --- a/weblogin +++ b/weblogin @@ -1 +1 @@ -Subproject commit d45baa7a10c3bfdcdfa51aadffa176ea690030b1 +Subproject commit e2b718f778bcd1d3597d0900df2b9f220f49833a