Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions JavaReleases/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
100 changes: 100 additions & 0 deletions JavaReleases/src/test/java/pl/mperor/lab/java/Java0.java
Original file line number Diff line number Diff line change
@@ -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<String> vector = new Vector<>();
vector.addElement("a");
vector.addElement("b");
Assertions.assertEquals("a", vector.firstElement());
Assertions.assertEquals("b", vector.elementAt(1));

Enumeration<String> elements = vector.elements();
Assertions.assertEquals("a", elements.nextElement());
Assertions.assertEquals("b", elements.nextElement());
Assertions.assertFalse(elements.hasMoreElements());

Hashtable<String, Integer> 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
}

}
130 changes: 77 additions & 53 deletions JavaReleases/src/test/java/pl/mperor/lab/java/Java1.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand All @@ -42,19 +66,26 @@ 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);

Assertions.assertEquals("Hello", bean.getStringField());
Assertions.assertEquals(-1, bean.getPrimitiveIntField());
Assertions.assertInstanceOf(Serializable.class, bean);

assertJavaBeanSerializationAndDeserialization(bean);
// getX/setX naming convention lets tools discover bean properties
List<String> 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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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));
}
}

}
13 changes: 13 additions & 0 deletions JavaReleases/src/test/java/pl/mperor/lab/java/Java5.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}

}
Loading