-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJava1.java
More file actions
347 lines (291 loc) · 15.2 KB
/
Copy pathJava1.java
File metadata and controls
347 lines (291 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
package pl.mperor.lab.java;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import pl.mperor.lab.java.lang.JavaBean;
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;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.sql.*;
import java.text.Collator;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import static pl.mperor.lab.java.lang.OuterClass.StaticNestedClass;
/**
* Java 1.1 (February 1997)
* <p>
* - LANGUAGE FEATURES:
* - Inner classes (member, local & anonymous)
* - Instance initializer blocks
* - Class literals (`String.class`)
* - Blank finals
* <p>
* - 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)
* <p>
* - TOOLS:
* - JAR files (`jar`)
* - Signed JARs (`javakey`)
*/
public class Java1 {
@Test
public void testInnerAndNestedStaticClasses() {
// Java 1.1 added nested classes - a static nested class needs no outer instance,
// while an inner class is bound to one and can access its members via OuterClass.this
StaticNestedClass nested = new StaticNestedClass();
Assertions.assertNotNull(nested);
OuterClass outer = new OuterClass();
InnerClass inner = outer.new InnerClass();
Assertions.assertEquals("outer secret", outer.getSecret());
Assertions.assertEquals(outer.getSecret(), inner.getOuterClassSecret());
Assertions.assertEquals("inner secret", inner.getSecret());
}
@Test
public void testLocalAndAnonymousClasses() {
// Local & anonymous classes can capture local variables - they had to be final in Java 1.1, effectively final since Java 8
String greeting = "Hello";
class LocalGreeter {
String greet(String name) {
return greeting + " " + name;
}
}
Object anonymous = new Object() {
@Override
public String toString() {
return greeting + " from anonymous";
}
};
Assertions.assertEquals("Hello John", new LocalGreeter().greet("John"));
Assertions.assertEquals("Hello from anonymous", anonymous.toString());
Assertions.assertTrue(LocalGreeter.class.isLocalClass());
Assertions.assertTrue(anonymous.getClass().isAnonymousClass());
}
@Test
public void testInstanceInitializerAndBlankFinal() {
// Instance initializer blocks run after field initializers (in textual order) and before the constructor body
var initialization = new Initialization(42);
Assertions.assertEquals(List.of("field", "initializer", "constructor"), initialization.steps);
// A blank final is declared without a value and must be assigned exactly once in every constructor
Assertions.assertEquals(42, initialization.id);
}
static class Initialization {
final List<String> steps = new ArrayList<>(List.of("field"));
final int id; // blank final
{
steps.add("initializer");
}
Initialization(int id) {
this.id = id;
steps.add("constructor");
}
}
@Test
public void testClassLiterals() {
// Class literals give the Class object at compile time - no instance or Class.forName lookup needed
Assertions.assertSame(String.class, "text".getClass());
Assertions.assertEquals("java.lang.String", String.class.getName());
// Primitives, arrays & void have their own Class objects, distinct from the wrapper classes
Assertions.assertTrue(int.class.isPrimitive());
Assertions.assertSame(int.class, Integer.TYPE);
Assertions.assertNotSame(int.class, Integer.class);
Assertions.assertSame(void.class, Void.TYPE);
Assertions.assertEquals("[I", int[].class.getName());
}
@Test
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);
// 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);
}
@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);
}
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
var beanFromFile = (JavaBean) in.readObject();
Assertions.assertEquals(bean.getStringField(), beanFromFile.getStringField());
Assertions.assertEquals(bean.getPrimitiveIntField(), beanFromFile.getPrimitiveIntField());
}
}
@Test
public void testReflectionAPI() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<JavaBean> clazz = JavaBean.class;
Constructor<JavaBean> constructor = clazz.getDeclaredConstructor(String.class, int.class);
JavaBean bean = constructor.newInstance("init by reflection", -1);
Method getter = clazz.getDeclaredMethod("getStringField");
Assertions.assertEquals("init by reflection", getter.invoke(bean));
Method setter = clazz.getDeclaredMethod("setStringField", String.class);
setter.invoke(bean, "set by reflection");
Assertions.assertEquals("set by reflection", getter.invoke(bean));
}
@Test
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 {
String getMessage() throws RemoteException;
}
static class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
protected HelloServiceImpl() throws RemoteException {
}
@Override
public String getMessage() throws RemoteException {
return "Hello World!";
}
}
@Test
public void testJavaDatabaseConnectivityAkaJDBC() throws SQLException, ClassNotFoundException {
String createTableUsersSqlCommand = "CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name TEXT)";
String insertUserSqlCommand = "INSERT INTO users (name) VALUES ('John Doe')";
String queryAllUsersSqlCommand = "SELECT * FROM users";
// 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
try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:test", "sa", "");
Statement statement = connection.createStatement()) {
statement.executeUpdate(createTableUsersSqlCommand);
statement.executeUpdate(insertUserSqlCommand);
try (ResultSet resultSet = statement.executeQuery(queryAllUsersSqlCommand)) {
Assertions.assertTrue(resultSet.next());
Assertions.assertEquals(1, resultSet.getInt("id"));
Assertions.assertEquals("John Doe", resultSet.getString("name"));
}
}
}
@Test
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));
}
}
@Test
public void testInternationalizationFormatting() {
// Java 1.1 added java.text - numbers, dates & messages are formatted according to a Locale
Locale poland = Locale.of("pl", "PL");
Assertions.assertEquals("1,234.5", NumberFormat.getInstance(Locale.US).format(1234.5));
Assertions.assertEquals("1.234,5", NumberFormat.getInstance(Locale.GERMANY).format(1234.5));
Assertions.assertEquals("$1,234.50", NumberFormat.getCurrencyInstance(Locale.US).format(1234.5));
// Month & day names are localized - Polish uses the genitive month form inside a full date
var newYear = new GregorianCalendar(2024, Calendar.JANUARY, 1).getTime();
Assertions.assertEquals("Monday, January 1, 2024", new SimpleDateFormat("EEEE, MMMM d, yyyy", Locale.US).format(newYear));
Assertions.assertEquals("poniedziałek, 1 stycznia 2024", new SimpleDateFormat("EEEE, d MMMM yyyy", poland).format(newYear));
// MessageFormat fills placeholders, ChoiceFormat picks the text variant based on a number
var messageFormat = new MessageFormat("{0} has {1,choice,0#no messages|1#one message|1<{1,number,integer} messages}", Locale.US);
Assertions.assertEquals("John Doe has no messages", messageFormat.format(new Object[]{"John Doe", 0}));
Assertions.assertEquals("John Doe has one message", messageFormat.format(new Object[]{"John Doe", 1}));
Assertions.assertEquals("John Doe has 1,000 messages", messageFormat.format(new Object[]{"John Doe", 1000}));
// String.compareTo compares UTF-16 code units, Collator applies the language's alphabetical order
List<String> words = new ArrayList<>(List.of("zebra", "ćma", "cebula", "ananas"));
words.sort(null);
Assertions.assertEquals(List.of("ananas", "cebula", "zebra", "ćma"), words);
words.sort(Collator.getInstance(poland));
Assertions.assertEquals(List.of("ananas", "cebula", "ćma", "zebra"), words);
}
@Test
public void testResourceBundle() {
// ResourceBundle picks the most specific bundle for a Locale (Messages_pl) and falls back to its parent (Messages)
String baseName = Messages.class.getName();
ResourceBundle polish = ResourceBundle.getBundle(baseName, Locale.of("pl"));
ResourceBundle root = ResourceBundle.getBundle(baseName, Locale.ROOT);
Assertions.assertEquals("Cześć", polish.getString("greeting"));
Assertions.assertEquals("Hello", root.getString("greeting"));
Assertions.assertEquals("JavaLab", polish.getString("app.name")); // missing in Messages_pl - taken from the parent
Assertions.assertThrows(MissingResourceException.class, () -> root.getString("missing"));
}
// Bundles can be classes (ListResourceBundle) or .properties files (PropertyResourceBundle)
public static class Messages extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][]{{"greeting", "Hello"}, {"app.name", "JavaLab"}};
}
}
public static class Messages_pl extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][]{{"greeting", "Cześć"}};
}
}
@Test
public void testArbitraryPrecisionArithmetic() {
// Java 1.1 added java.math - double cannot represent most decimal fractions exactly, BigDecimal can
Assertions.assertNotEquals(0.3, 0.1 + 0.2);
Assertions.assertEquals(new BigDecimal("0.3"), new BigDecimal("0.1").add(new BigDecimal("0.2")));
// BigDecimal(double) keeps the binary approximation of the double - prefer the String constructor
Assertions.assertNotEquals(new BigDecimal("0.1"), new BigDecimal(0.1));
Assertions.assertEquals(new BigDecimal("0.1"), BigDecimal.valueOf(0.1)); // valueOf (Java 5) goes through Double.toString
// equals compares value & scale, while compareTo compares only the numerical value
Assertions.assertNotEquals(new BigDecimal("2.0"), new BigDecimal("2.00"));
Assertions.assertEquals(0, new BigDecimal("2.0").compareTo(new BigDecimal("2.00")));
// long silently overflows, while BigInteger has no fixed upper bound
Assertions.assertEquals(Long.MIN_VALUE, Long.MAX_VALUE + 1);
BigInteger beyondLong = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
Assertions.assertEquals(new BigInteger("9223372036854775808"), beyondLong);
}
}