From 0f1b3d326efbf305556c9e9229b87e97fc9f76c8 Mon Sep 17 00:00:00 2001 From: mperor Date: Thu, 24 Sep 2026 20:26:44 +0200 Subject: [PATCH] Review Java 1.1 tests and add Java 1.0 examples --- JavaReleases/README.md | 1 + .../test/java/pl/mperor/lab/java/Java0.java | 100 ++++++++++++++ .../test/java/pl/mperor/lab/java/Java1.java | 130 +++++++++++------- .../test/java/pl/mperor/lab/java/Java5.java | 13 ++ 4 files changed, 191 insertions(+), 53 deletions(-) create mode 100644 JavaReleases/src/test/java/pl/mperor/lab/java/Java0.java diff --git a/JavaReleases/README.md b/JavaReleases/README.md index 979b5fc..f748b7b 100644 --- a/JavaReleases/README.md +++ b/JavaReleases/README.md @@ -4,6 +4,7 @@ This project includes unit tests for key functionalities introduced in each Java version. +- [Java 1.0 (Java 0)](src/test/java/pl/mperor/lab/java/Java0.java) - [Java 1.1 (Java 1™)](src/test/java/pl/mperor/lab/java/Java1.java) - [Java 1.2 (Java 2™)](src/test/java/pl/mperor/lab/java/Java2.java) - [Java 1.3 (Java 3™)](src/test/java/pl/mperor/lab/java/Java3.java) diff --git a/JavaReleases/src/test/java/pl/mperor/lab/java/Java0.java b/JavaReleases/src/test/java/pl/mperor/lab/java/Java0.java new file mode 100644 index 0000000..4e545a5 --- /dev/null +++ b/JavaReleases/src/test/java/pl/mperor/lab/java/Java0.java @@ -0,0 +1,100 @@ +package pl.mperor.lab.java; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * Java 1.0 (January 1996) + * + * - LANGUAGE FEATURES: + * - Classes & interfaces (single inheritance, multiple interface implementation) + * - Packages & access modifiers + * - Exceptions (checked & unchecked) + * - Threads & `synchronized` built into the language + * - Garbage collection + * - 16-bit Unicode `char` + * + * - LIBRARIES & APIs: + * - Legacy collections (`Vector`, `Hashtable`, `Stack`, `Enumeration`, `BitSet`) + * - Byte streams (`InputStream` / `OutputStream`) + * - Networking (`Socket`, `ServerSocket`, `URL`) + * - Utilities (`Date`, `Random`, `Properties`, `StringTokenizer`, `Observable` / `Observer`) + * - AWT (Abstract Window Toolkit) + * - Applets (`java.applet`) + * + * - TOOLS: + * - `javac`, `java`, `javadoc`, `jdb`, `javap` + * - `appletviewer` + * - `javah` (native headers) + */ +public class Java0 { + + @Test + public void testLegacyCollections() { + // Before the Collections Framework (Java 1.2) there were only Vector, Hashtable & Enumeration (all synchronized) + Vector vector = new Vector<>(); + vector.addElement("a"); + vector.addElement("b"); + Assertions.assertEquals("a", vector.firstElement()); + Assertions.assertEquals("b", vector.elementAt(1)); + + Enumeration elements = vector.elements(); + Assertions.assertEquals("a", elements.nextElement()); + Assertions.assertEquals("b", elements.nextElement()); + Assertions.assertFalse(elements.hasMoreElements()); + + Hashtable hashtable = new Hashtable<>(); + hashtable.put("one", 1); + Assertions.assertEquals(1, hashtable.get("one")); + Assertions.assertThrows(NullPointerException.class, () -> hashtable.put("two", null)); // null keys & values are not allowed + } + + @Test + @Timeout(5) + public void testWaitAndNotify() throws InterruptedException { + // Before java.util.concurrent (Java 5) threads were coordinated only with synchronized, wait() & notify() + var mailbox = new Mailbox(); + Thread producer = new Thread(() -> mailbox.put("Hello")); + producer.start(); + + Assertions.assertEquals("Hello", mailbox.take()); // blocks until the producer puts a message + producer.join(); + } + + static class Mailbox { + private String message; + + synchronized void put(String message) { + this.message = message; + notify(); + } + + synchronized String take() throws InterruptedException { + while (message == null) { // loop guards against spurious wake-ups + wait(); + } + return message; + } + } + + @Test + public void testUnicodeSupport() { + // Since Java 1.0 `char` is a 16-bit Unicode code unit, covering the Basic Multilingual Plane (BMP) + char englishLetter = '\u0041'; // Latin capital letter A (A) + char polishLetter = '\u0104'; // Latin capital letter A with ogonek (Ą) + char greekLetter = '\u0391'; // Greek capital letter Alpha (Α) + char chineseCharacter = '\u4E2D'; // Chinese character for "middle" (中) + + Assertions.assertEquals('A', englishLetter); + Assertions.assertEquals('Ą', polishLetter); + Assertions.assertEquals('Α', greekLetter); + Assertions.assertEquals('中', chineseCharacter); + Assertions.assertNotEquals(englishLetter, greekLetter); // look alike, but different characters + } + +} diff --git a/JavaReleases/src/test/java/pl/mperor/lab/java/Java1.java b/JavaReleases/src/test/java/pl/mperor/lab/java/Java1.java index 796142c..5f6f6e4 100644 --- a/JavaReleases/src/test/java/pl/mperor/lab/java/Java1.java +++ b/JavaReleases/src/test/java/pl/mperor/lab/java/Java1.java @@ -6,6 +6,9 @@ import pl.mperor.lab.java.lang.OuterClass; import pl.mperor.lab.java.lang.OuterClass.InnerClass; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -17,14 +20,35 @@ import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; import java.sql.*; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.Arrays; +import java.util.List; import static pl.mperor.lab.java.lang.OuterClass.StaticNestedClass; /** * Java 1.1 (February 1997) + * + * - LANGUAGE FEATURES: + * - Inner classes (member, local & anonymous) + * - Instance initializer blocks + * - Class literals (`String.class`) + * - Blank finals + * + * - LIBRARIES & APIs: + * - JavaBeans + * - JDBC (Java Database Connectivity) + * - RMI (Remote Method Invocation) + * - Reflection API + * - Object Serialization + * - Character Streams (`Reader` / `Writer`) & Unicode 2.0 + * - Internationalization (`java.text`, `Locale`, `ResourceBundle`) + * - Arbitrary-Precision Arithmetic (`java.math`) + * - AWT Event Delegation Model + * - JNI (Java Native Interface) + * + * - TOOLS: + * - JAR files (`jar`) + * - Signed JARs (`javakey`) */ public class Java1 { @@ -42,8 +66,8 @@ public void testInnerAndNestedStaticClasses() { } @Test - public void testJavaBean() throws IOException, ClassNotFoundException { - var bean = new JavaBean(); + public void testJavaBean() throws IntrospectionException { + var bean = new JavaBean(); // public no-arg constructor bean.setStringField("Hello"); bean.setPrimitiveIntField(-1); @@ -51,10 +75,17 @@ public void testJavaBean() throws IOException, ClassNotFoundException { Assertions.assertEquals(-1, bean.getPrimitiveIntField()); Assertions.assertInstanceOf(Serializable.class, bean); - assertJavaBeanSerializationAndDeserialization(bean); + // getX/setX naming convention lets tools discover bean properties + List properties = Arrays.stream(Introspector.getBeanInfo(JavaBean.class, Object.class).getPropertyDescriptors()) + .map(PropertyDescriptor::getName) + .toList(); + Assertions.assertEquals(List.of("primitiveIntField", "stringField"), properties); } - private void assertJavaBeanSerializationAndDeserialization(JavaBean bean) throws IOException, ClassNotFoundException { + @Test + public void testObjectSerialization() throws IOException, ClassNotFoundException { + var bean = new JavaBean("Hello", -1); + var file = new File("src/test/resources/bean.bin"); try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) { out.writeObject(bean); @@ -83,33 +114,23 @@ public void testReflectionAPI() throws NoSuchMethodException, InstantiationExcep } @Test - public void testRemoteMethodInvocationAkaRMI() throws RemoteException, NotBoundException, InterruptedException { - CountDownLatch serverReadyLatch = new CountDownLatch(1); - - // Run the RMI server asynchronously - ExecutorService executor = Executors.newSingleThreadExecutor(); - executor.submit(() -> { - try { - HelloService helloService = new HelloServiceImpl(); - Registry registry = LocateRegistry.createRegistry(1099); - registry.rebind("HelloService", helloService); - serverReadyLatch.countDown(); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - }); - - // Give the server time to start - serverReadyLatch.await(); - - // Connect to the RMI registry and lookup the HelloService - Registry registry = LocateRegistry.getRegistry("localhost", 1099); - HelloService stub = (HelloService) registry.lookup("HelloService"); - - // Call the remote method and verify the result - Assertions.assertEquals("Hello World!", stub.getMessage()); - - executor.shutdown(); + public void testRemoteMethodInvocationAkaRMI() throws RemoteException, NotBoundException { + // Server side: start the registry, export the remote object and bind it under a name + Registry serverRegistry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT); + HelloService helloService = new HelloServiceImpl(); + try { + serverRegistry.rebind("HelloService", helloService); + + // Client side: look up the stub in the registry and call the remote method + Registry clientRegistry = LocateRegistry.getRegistry("localhost", Registry.REGISTRY_PORT); + HelloService stub = (HelloService) clientRegistry.lookup("HelloService"); + + Assertions.assertEquals("Hello World!", stub.getMessage()); + } finally { + // Release the port and RMI threads, so the test can be run again in the same JVM + UnicastRemoteObject.unexportObject(helloService, true); + UnicastRemoteObject.unexportObject(serverRegistry, true); + } } interface HelloService extends Remote { @@ -133,7 +154,8 @@ public void testJavaDatabaseConnectivityAkaJDBC() throws SQLException, ClassNotF String insertUserSqlCommand = "INSERT INTO users (name) VALUES ('Mark Pi')"; String queryAllUsersSqlCommand = "SELECT * FROM users"; - // Load the H2 JDBC driver + // Load the H2 JDBC driver: its static initializer registers it in DriverManager. + // Required in Java 1.1; since JDBC 4.0 (Java 6) drivers are discovered automatically via ServiceLoader. Class.forName("org.h2.Driver"); // Establish a connection to an in-memory H2 database & create a statement object for executing SQL queries @@ -142,28 +164,30 @@ public void testJavaDatabaseConnectivityAkaJDBC() throws SQLException, ClassNotF statement.executeUpdate(createTableUsersSqlCommand); statement.executeUpdate(insertUserSqlCommand); - ResultSet resultSet = statement.executeQuery(queryAllUsersSqlCommand); - - Assertions.assertTrue(resultSet.next()); - Assertions.assertEquals(1, resultSet.getInt("id")); - Assertions.assertEquals("Mark Pi", resultSet.getString("name")); + try (ResultSet resultSet = statement.executeQuery(queryAllUsersSqlCommand)) { + Assertions.assertTrue(resultSet.next()); + Assertions.assertEquals(1, resultSet.getInt("id")); + Assertions.assertEquals("Mark Pi", resultSet.getString("name")); + } } } @Test - public void testUnicodeSupport() { - // UTF-16 (ang. 16-bit Unicode Transformation Format) - char englishLetter = '\u0041'; // Latin Alphabet - char polishLetter = '\u0104'; // Polish capital letter A with a hook - diacritic (Ą) - char greekLetter = '\u0391'; // Greek capital letter Alpha (Α) - char chineseCharacter = '\u4E2D'; // Chinese character for "middle" (中) - String emoji = "\uD83D\uDE00"; // Smiling face emoji (😀) - - Assertions.assertEquals('A', englishLetter); - Assertions.assertEquals('Α', greekLetter); - Assertions.assertEquals('Ą', polishLetter); - Assertions.assertEquals('中', chineseCharacter); - Assertions.assertEquals("😀", emoji); + public void testCharacterStreams() throws IOException { + // Java 1.1 added character streams (Reader/Writer) converting chars <-> bytes with a given encoding + String text = "Zażółć gęślą jaźń"; // 17 chars, 9 of them Polish diacritics + Assertions.assertEquals(26, text.getBytes("UTF-8").length); // diacritics take 2 bytes each + Assertions.assertEquals(17, text.getBytes("Cp1250").length); // single-byte Windows code page + + var bytes = new ByteArrayOutputStream(); + try (Writer writer = new OutputStreamWriter(bytes, "UTF-8")) { + writer.write(text); + } + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes.toByteArray()), "UTF-8")) { + var buffer = new char[text.length()]; + Assertions.assertEquals(text.length(), reader.read(buffer)); + Assertions.assertEquals(text, new String(buffer)); + } } } \ No newline at end of file diff --git a/JavaReleases/src/test/java/pl/mperor/lab/java/Java5.java b/JavaReleases/src/test/java/pl/mperor/lab/java/Java5.java index 12f7a81..e01ac18 100644 --- a/JavaReleases/src/test/java/pl/mperor/lab/java/Java5.java +++ b/JavaReleases/src/test/java/pl/mperor/lab/java/Java5.java @@ -157,4 +157,17 @@ public void testCollectionsAPI() { Assertions.assertTrue(empty.isEmpty()); } + @Test + public void testSupplementaryCharacters() { + // JSR 204: characters beyond BMP (code point > U+FFFF) are stored as a surrogate pair of two chars + String emoji = "😀"; // U+1F600 Grinning face + + Assertions.assertEquals(2, emoji.length()); + Assertions.assertEquals(1, emoji.codePointCount(0, emoji.length())); + Assertions.assertEquals(0x1F600, emoji.codePointAt(0)); + Assertions.assertTrue(Character.isHighSurrogate(emoji.charAt(0))); + Assertions.assertTrue(Character.isLowSurrogate(emoji.charAt(1))); + Assertions.assertEquals("\uD83D\uDE00", new String(Character.toChars(0x1F600))); + } + }