diff --git a/.gitignore b/.gitignore index 32858aa..c2e3b9a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +#idea +*.iml +.idea diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE.html b/LICENSE.html new file mode 100644 index 0000000..4199a66 --- /dev/null +++ b/LICENSE.html @@ -0,0 +1,92 @@ + + +Java Examples in a Nutshell: License + + + +
+

License for the Examples from the Book
Java Examples +in a Nutshell, 2nd Edition

+
+ +

0. Definitions + +For the purposes of this license, "the book" means the +second edition of the book Java Examples in a +Nutshell. "The examples" means the Java programs and +other source code included in the book, and available for +download from the website +http://www.davidflanagan.com/javaexamples2 + + +

1. Copyright + +The examples are Copyright © 2000 by David Flanagan. +All rights reserved. + +

2. No Warranty + +The examples are provided as-is, with NO WARRANTY OF ANY KIND. +The examples were written to teach Java programming concepts +and techniques, and are not intended as production-quality +software. In particular, they have not been carefully +tested. Neither David Flanagan nor the publisher O'Reilly +& Associates is responsible for any loss or damages of +any kind resulting from the use of these examples. + +

3. Non-commercial Use + +You may study, use, modify, and distribute these examples +for any non-commercial purpose, as long as the copyright +notice at the top of each example is retained. You may use +the examples in software you develop for personal use, in +software used internally by non-profit institutions, and in +software that is publicly released under an open-source +license. + +

4. Educational Use + +If you are an educator at non-profit educational +institution, you may use the examples for educational +purposes in your courses. Please make the book a required +or recommended text for the students in your course. +

+If you are an educator at a for-profit educational +institution or at an organization whose primary purpose is +not education, you may also use the examples in your +courses, but you must ensure that each of your students +purchases or is given a copy of the book. + +

5. Commercial Use + +The examples were developed to demonstrate Java programming +concepts and techniques. They are not intended as +production code, and have not been through any kind of +rigorous testing or validation. Nevertheless, if you find +the code useful, and would like to use it commercially, you +may purchase a commercial use license for a nominal fee. +Visit http://www.davidflanagan.com/javaexamples2/ for +information on obtaining such a license. +

+Upon payment, a commercial use license entitles you to use +the examples and modified versions of the examples in +commercial software. "Commercial software" includes +software written for sale and software written for internal +use by a for-profit organization (including your own +organization) It does not include software that you write +solely for your personal use, software written for internal +use by non-profit organizations, and software that is +publicly released under an open-source license. +

+Note that a commercial-use license does not entitle to you +sell or otherwise commercially distribute the examples or +modified versions of the examples as examples. You may only +commercially distribute the examples when they are +integrated into your commercial software. +

+Note that purchase of a commercial-use license does not +provide any kind of warranty, expressed or implied, for the +examples + + + diff --git a/README.md b/README.md index 6987d76..3b508db 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # java-examples source code from book java examples in a nutshell + +# source README.MD + +These are the examples from the book Java Examples in a Nutshell, 2nd +Edition, by David Flanagan. + +See the file index.html for more information. diff --git a/cover.gif b/cover.gif new file mode 100644 index 0000000..a2dd97a Binary files /dev/null and b/cover.gif differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..42de93e --- /dev/null +++ b/index.html @@ -0,0 +1,263 @@ + + +Examples From Java Examples in a Nutshell + + +

+

Examples From Java Examples in a Nutshell, 2nd Edition

+
+ + + + + +This directory contains the complete set of examples from the book +Java Examples in a Nutshell, 2nd Edition. + +

Get the Book

+If you don't already have a copy of the book, you should get +one: it provides the context and explanation necessary to +get the most out of the examples. + +You can buy a copy of the book from: + + +

Legal Matters

+ +The examples are Copyright © 2000 by David Flanagan. +They are free for non-commercial use, but you must purchase +a license if you want to use them commercially. The +examples are provided as-is, with NO WARRANTY OF ANY KIND. +See the license for complete +details. + +

Working with the Examples

+ +The Java examples are all defined in sub-packages of the +package com.davidflanagan.examples. This means +that you'll find the example source code in subdirectories +of the directory +com/davidflanagan/examples/ +

+In order to work most easily with the examples, you'll need +to add the current directory (the directory that you're +reading this file from) into your CLASSPATH environment +variable. +

+In the book, the title of each example gives its file +name, and the package statement in each example +shows what package it is a part of. With this information, +you can easily find the source code for the example you +want. For browsing convenience, however, the examples are +also listed here by chapter, number, and name: + +

Chapter 1: Java Basics

+1-1 Hello.java
+1-2 FizzBuzz.java
+1-3 Fibonacci.java
+1-4 Echo.java
+1-5 Reverse.java
+1-6 FizzBuzz2.java
+1-7 Factorial.java
+1-8 Factorial2.java
+1-9 Factorial3.java
+1-10 Factorial4.java
+1-11 FactComputer.java
+1-12 FactQuoter.java
+1-13 Rot13Input.java
+1-14 SortNumbers.java
+1-15 Sieve.java
+ +

Chapter 2: Objects, Classes, and Interfaces

+2-1 Rect.java
+2-2 RectTest.java
+2-3 DrawableRect.java
+2-4 ColoredRect.java
+2-5 ComplexNumber.java
+2-6 Randomizer.java
+2-7 Averager.java
+2-8 LinkedList.java
+2-9 Sorter.java
+ +

Chapter 3: Input/Output

+3-1 Delete.java
+3-2 FileCopy.java
+3-3 FileViewer.java
+3-4 FileLister.java
+3-5 Compress.java
+3-6 RemoveHTMLReader.java
+3-7 GrepReader.java
+3-8 HTMLWriter.java
+ +

Chapter 4: Threads

+4-1 ThreadDemo.java
+4-2 ThreadLister.java
+4-3 Deadlock.java
+4-4 TimerTask.java
+4-5 Timer.java
+ +

Chapter 5: Networking

+5-1 GetURL.java
+5-2 GetURLInfo.java
+5-3 SendMail.java
+5-4 HttpClient.java
+5-5 HttpMirror.java
+5-6 SimpleProxyServer.java
+5-7 Who.java
+5-8 GenericClient.java
+5-9 Server.java
+5-10 ProxyServer.java
+5-11 UDPSend.java
+5-12 UDPReceive.java
+ +

Chapter 6: Security and Cryptography

+6-1 SafeServer.java
+6-2 SafeServer.policy
+6-3 SecureService.java
+6-4 Manifest.java
+6-5 TripleDES.java
+ +

Chapter 7: Internationalization

+7-1 UnicodeDisplay.java
+7-2 ConvertEncoding.java
+7-3 Portfolio.java
+7-4 SimpleMenu.java
+7-5 LocalizedError.java
+ +

Chapter 8: Reflection

+8-1 ShowClass.java
+8-2 Command.java
+ +

Chapter 9: Object Serialization

+9-1 Serializer.java
+9-2 IntList.java
+9-3 CompactIntList.java
+ +

Chapter 10: Graphical User Interfaces

+10-1 ShowComponent.java
+10-2 Containers.java
+10-3 FlowLayoutPane.java
+10-4 GridLayoutPane.java
+10-5 BorderLayoutPane.java
+10-6 BoxLayoutPane.java
+10-7 GridBagLayoutPane.java
+10-8 NullLayoutPane.java
+10-9 ColumnLayout.java
+10-10 ColumnLayoutPane.java
+10-11 ScribblePane1.java
+10-12 ScribblePane2.java
+10-13 ScribblePane3.java
+10-14 ScribblePane4.java
+10-15 ItemChooser.java
+10-16 Scribble.java
+10-17 CommandAction.java
+10-18 FontChooser.java
+10-19 PropertyTable.java
+10-20 ComponentTree.java
+10-21 WebBrowser.java
+10-22 GUIResourceBundle.java
+10-23 ResourceParser.java
+10-24 CommandParser.java
+10-25 ActionParser.java
+10-26 MenuBarParser.java
+10-27 MenuParser.java
+10-28 ThemeManager.java
+10-29 AppletMenuBar.java
+ +

Chapter 11: Graphics

+11-1 GraphicsSampler.java
+11-2 FontList.java
+11-3 ColorGradient.java
+11-4 BouncingCircle.java
+11-5 GraphicsExample.java
+11-6 Shapes.java
+11-7 Transforms.java
+11-8 LineStyles.java
+11-9 Stroking.java
+11-10 Paints.java
+11-11 AntiAlias.java
+11-12 CompositeEffects.java
+11-13 ImageOps.java
+11-14 Spiral.java
+11-15 CustomStrokes.java
+11-16 GenericPaint.java
+11-17 Hypnosis.java
+11-18 GraphicsExampleFrame.java
+ +

Chapter 12: Printing

+12-1 ScribblePrinter1.java
+12-2 ScribblePrinter2.java
+12-3 HardcopyWriter.java
+12-4 PrintableDocument.java
+ +

Chapter 13: Data Transfer

+13-1 S +impleCutAndPaste.java
+13-2 Scribble.java
+13-3 ScribbleCutAndPaste.java
+13-4 ScribbleDragAndDrop.java
+ +

Chapter 14: JavaBeans

+14-1 MultiLineLabel.java
+14-2 Alignment.java
+14-3 YesNoPanel.java
+14-4 AnswerEvent.java
+14-5 AnswerListener.java
+14-6 YesNoPanelBeanInfo.java
+14-7 AlignmentEditor.java
+14-8 YesNoPanelMessageEditor.java
+14-9 YesNoPanelCustomizer.java
+ +

Chapter 15: Applets

+15-1 FirstApplet.java
+15-2 Clock.java
+15-3 Scribble.java
+15-4 EventTester.java
+15-5 ColorScribble.java
+15-6 Soundmap.java
+ +

Chapter 16: Remote Method Invocation

+16-1 Bank.java
+16-2 RemoteBankServer.java
+16-3 Mud.java
+16-4 MudServer.java
+16-5 MudPlace.java
+16-6 MudPerson.java
+16-7 MudClient.java
+ +

Chapter 17: Database Access with SQL

+17-1 ExecuteSQL.java
+17-2 GetDBInfo.java
+17-3 MakeAPIDB.java
+17-4 LookupAPI.java
+17-5 RemoteDBBankServer.java
+ +

Chapter 18: Servlets and JSP

+18-1 Hello.java
+18-2 Counter.java
+18-3 Query.java
+18-4 login.jsp
+18-5 forcelogin.jsp
+18-6 portal.jsp
+18-7 UserBean.java
+18-8 Logout.java
+18-9 DecorBox.java
+18-10 WEB-INF/tlds/decor_0_1.tld
+18-11 web.xml
+18-12 makewar.sh: a script for packaging a web application
+ +

Chapter 19: XML

+19-1 ListServlets1.java
+19-2 ListServlets2.java
+19-3 WebAppConfig.java
+19-4 XMLDocumentWriter.java
+19-5 DOMTreeWalkerTreeModel.java
+19-6 WebAppConfig2.java + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..57b6b19 --- /dev/null +++ b/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + org.dean + java-examples + 1.0-SNAPSHOT + + + \ No newline at end of file diff --git a/src/main/java/com/davidflanagan/examples/applet/Clock.html b/src/main/java/com/davidflanagan/examples/applet/Clock.html new file mode 100644 index 0000000..031c3fe --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/Clock.html @@ -0,0 +1,4 @@ + + diff --git a/src/main/java/com/davidflanagan/examples/applet/Clock.java b/src/main/java/com/davidflanagan/examples/applet/Clock.java new file mode 100644 index 0000000..7aca69f --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/Clock.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.applet; +import java.applet.*; // Don't forget this import statement! +import java.awt.*; // Or this one for the graphics! +import java.util.Date; // To obtain the current time +import java.text.DateFormat; // For displaying the time + +/** + * This applet displays the time, and updates it every second + **/ +public class Clock extends Applet implements Runnable { + Label time; // A component to display the time in + DateFormat timeFormat; // This object converts the time to a string + Thread timer; // The thread that updates the time + volatile boolean running; // A flag used to stop the thread + + /** + * The init method is called when the browser first starts the applet. + * It sets up the Label component and obtains a DateFormat object + **/ + public void init() { + time = new Label(); + time.setFont(new Font("helvetica", Font.BOLD, 12)); + time.setAlignment(Label.CENTER); + setLayout(new BorderLayout()); + add(time, BorderLayout.CENTER); + timeFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM); + } + + /** + * This browser calls this method to tell the applet to start running. + * Here, we create and start a thread that will update the time each + * second. Note that we take care never to have more than one thread + **/ + public void start() { + running = true; // Set the flag + if (timer == null) { // If we don't already have a thread + timer = new Thread(this); // Then create one + timer.start(); // And start it running + } + } + + /** + * This method implements Runnable. It is the body of the thread. Once + * a second, it updates the text of the Label to display the current time + **/ + public void run() { + while(running) { // Loop until we're stopped + // Get current time, convert to a String, and display in the Label + time.setText(timeFormat.format(new Date())); + // Now wait 1000 milliseconds + try { Thread.sleep(1000); } + catch (InterruptedException e) {} + } + // If the thread exits, set it to null so we can create a new one + // if start() is called again. + timer = null; + } + + /** + * The browser calls this method to tell the applet that it is not visible + * and should not run. It sets a flag that tells the run() method to exit + **/ + public void stop() { running = false; } + + /** + * Returns information about the applet for display by the applet viewer + **/ + public String getAppletInfo() { + return "Clock applet Copyright (c) 2000 by David Flanagan"; + } +} diff --git a/src/main/java/com/davidflanagan/examples/applet/ColorScribble.html b/src/main/java/com/davidflanagan/examples/applet/ColorScribble.html new file mode 100644 index 0000000..8094414 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/ColorScribble.html @@ -0,0 +1,6 @@ + + + + diff --git a/src/main/java/com/davidflanagan/examples/applet/ColorScribble.java b/src/main/java/com/davidflanagan/examples/applet/ColorScribble.java new file mode 100644 index 0000000..2a79064 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/ColorScribble.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.applet; +import java.applet.*; +import java.awt.*; + +/** + * A version of the Scribble applet that reads two applet parameters + * to set the foreground and background colors. It also returns + * information about itself when queried. + **/ +public class ColorScribble extends Scribble { + // Read in two color parameters and set the colors. + public void init() { + super.init(); // Let the superclass initialize itself + Color foreground = getColorParameter("foreground"); + Color background = getColorParameter("background"); + if (foreground != null) this.setForeground(foreground); + if (background != null) this.setBackground(background); + } + + // Read the specified parameter. Interpret it as a hexadecimal + // number of the form RRGGBB and convert it to a color. + protected Color getColorParameter(String name) { + String value = this.getParameter(name); + try { return new Color(Integer.parseInt(value, 16)); } + catch (Exception e) { return null; } + } + + // Return information suitable for display in an About dialog box. + public String getAppletInfo() { + return "ColorScribble v. 0.03. Written by David Flanagan."; + } + + // Return info about the supported parameters. Web browsers and applet + // viewers should display this information, and may also allow users to + // set the parameter values. + public String[][] getParameterInfo() { return info; } + + // Here's the information that getParameterInfo() returns. + // It is an array of arrays of strings describing each parameter. + // Format: parameter name, parameter type, parameter description + private String[][] info = { + {"foreground", "hexadecimal color value", "foreground color"}, + {"background", "hexadecimal color value", "background color"} + }; +} diff --git a/src/main/java/com/davidflanagan/examples/applet/EventTester.html b/src/main/java/com/davidflanagan/examples/applet/EventTester.html new file mode 100644 index 0000000..17e955f --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/EventTester.html @@ -0,0 +1,4 @@ + + diff --git a/src/main/java/com/davidflanagan/examples/applet/EventTester.java b/src/main/java/com/davidflanagan/examples/applet/EventTester.java new file mode 100644 index 0000000..c5dad0a --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/EventTester.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.applet; +import java.applet.*; +import java.awt.*; +import java.util.*; + +/** An applet that gives details about Java 1.0 events */ +public class EventTester extends Applet { + // Handle mouse events + public boolean mouseDown(Event e, int x, int y) { + showLine(mods(e.modifiers) + "Mouse Down: [" + x + "," + y + "]"); + return true; + } + public boolean mouseUp(Event e, int x, int y) { + showLine(mods(e.modifiers) + "Mouse Up: [" + x + "," + y + "]"); + return true; + } + public boolean mouseDrag(Event e, int x, int y) { + showLine(mods(e.modifiers) + "Mouse Drag: [" + x + "," + y + "]"); + return true; + } + public boolean mouseMove(Event e, int x, int y) { + showLine(mods(e.modifiers) + "Mouse Move: [" + x + "," + y + "]"); + return true; + } + public boolean mouseEnter(Event e, int x, int y) { + showLine("Mouse Enter: [" + x + "," + y + "]"); return true; + } + public boolean mouseExit(Event e, int x, int y) { + showLine("Mouse Exit: [" + x + "," + y + "]"); return true; + } + + // Handle focus events + public boolean gotFocus(Event e, Object what) { + showLine("Got Focus"); return true; + } + public boolean lostFocus(Event e, Object what) { + showLine("Lost Focus"); return true; + } + + // Handle key down and key up events + // This gets more confusing because there are two types of key events + public boolean keyDown(Event e, int key) { + int flags = e.modifiers; + if (e.id == Event.KEY_PRESS) // a regular key + showLine("Key Down: " + mods(flags) + key_name(e)); + else if (e.id == Event.KEY_ACTION) // a function key + showLine("Function Key Down: " + mods(flags) + + function_key_name(key)); + return true; + } + public boolean keyUp(Event e, int key) { + int flags = e.modifiers; + if (e.id == Event.KEY_RELEASE) // a regular key + showLine("Key Up: " + mods(flags) + key_name(e)); + else if (e.id == Event.KEY_ACTION_RELEASE) // a function key + showLine("Function Key Up: " + mods(flags) + + function_key_name(key)); + return true; + } + + // The remaining methods help us sort out the various modifiers and keys + + // Return the current list of modifier keys + private String mods(int flags) { + String s = "[ "; + if (flags == 0) return ""; + if ((flags & Event.SHIFT_MASK) != 0) s += "Shift "; + if ((flags & Event.CTRL_MASK) != 0) s += "Control "; + if ((flags & Event.META_MASK) != 0) s += "Meta "; + if ((flags & Event.ALT_MASK) != 0) s += "Alt "; + s += "] "; + return s; + } + + // Return the name of a regular (non-function) key. + private String key_name(Event e) { + char c = (char) e.key; + if (e.controlDown()) { // If CTRL flag is set, handle control chars. + if (c < ' ') { + c += '@'; + return "^" + c; + } + } + else { // If CTRL flag is not set, then certain ASCII + switch (c) { // control characters have special meaning. + case '\n': return "Return"; + case '\t': return "Tab"; + case '\033': return "Escape"; + case '\010': return "Backspace"; + } + } + // Handle the remaining possibilities. + if (c == '\177') return "Delete"; + else if (c == ' ') return "Space"; + else return String.valueOf(c); + } + + // Return the name of a function key. Just compare the key to the + // constants defined in the Event class. + private String function_key_name(int key) { + switch(key) { + case Event.HOME: return "Home"; case Event.END: return "End"; + case Event.PGUP: return "Page Up"; case Event.PGDN: return"Page Down"; + case Event.UP: return "Up"; case Event.DOWN: return "Down"; + case Event.LEFT: return "Left"; case Event.RIGHT: return "Right"; + case Event.F1: return "F1"; case Event.F2: return "F2"; + case Event.F3: return "F3"; case Event.F4: return "F4"; + case Event.F5: return "F5"; case Event.F6: return "F6"; + case Event.F7: return "F7"; case Event.F8: return "F8"; + case Event.F9: return "F9"; case Event.F10: return "F10"; + case Event.F11: return "F11"; case Event.F12: return "F12"; + } + return "Unknown Function Key"; + } + + /** A list of lines to display in the window */ + protected Vector lines = new Vector(); + /** Add a new line to the list of lines, and redisplay */ + protected void showLine(String s) { + if (lines.size() == 20) lines.removeElementAt(0); + lines.addElement(s); + repaint(); + } + /** This method repaints the text in the window */ + public void paint(Graphics g) { + for(int i = 0; i < lines.size(); i++) + g.drawString((String)lines.elementAt(i), 20, i*16 + 50); + } +} diff --git a/src/main/java/com/davidflanagan/examples/applet/FirstApplet.html b/src/main/java/com/davidflanagan/examples/applet/FirstApplet.html new file mode 100644 index 0000000..68b11e1 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/FirstApplet.html @@ -0,0 +1,4 @@ + + diff --git a/src/main/java/com/davidflanagan/examples/applet/FirstApplet.java b/src/main/java/com/davidflanagan/examples/applet/FirstApplet.java new file mode 100644 index 0000000..9910a63 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/FirstApplet.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.applet; +import java.applet.*; // Don't forget this import statement! +import java.awt.*; // Or this one for the graphics! + +/** This applet just says "Hello World! */ +public class FirstApplet extends Applet { + // This method displays the applet. + public void paint(Graphics g) { + g.drawString("Hello World", 25, 50); + } +} diff --git a/src/main/java/com/davidflanagan/examples/applet/Scribble.html b/src/main/java/com/davidflanagan/examples/applet/Scribble.html new file mode 100644 index 0000000..106f53e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/Scribble.html @@ -0,0 +1,4 @@ + + diff --git a/src/main/java/com/davidflanagan/examples/applet/Scribble.java b/src/main/java/com/davidflanagan/examples/applet/Scribble.java new file mode 100644 index 0000000..d6c8d00 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/Scribble.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.applet; +import java.applet.*; +import java.awt.*; + +/** + * This applet lets the user scribble with the mouse. + * It demonstrates the Java 1.0 event model. + **/ +public class Scribble extends Applet { + private int lastx, lasty; // Remember last mouse coordinates. + Button erase_button; // The Erase button. + + /** Initialize the erase button, ask for keyboard focus */ + public void init() { + erase_button = new Button("Erase"); + this.add(erase_button); + this.setBackground(Color.white); // Set background color for scribble + this.requestFocus(); // Ask for keyboard focus so we get key events + } + + /** Respond to mouse clicks */ + public boolean mouseDown(Event e, int x, int y) { + lastx = x; lasty = y; // Remember where the click was + return true; + } + + /** Respond to mouse drags */ + public boolean mouseDrag(Event e, int x, int y) { + Graphics g = getGraphics(); + g.drawLine(lastx, lasty, x, y); // Draw from last position to here + lastx = x; lasty = y; // And remember new last position + return true; + } + + /** Respond to key presses: Erase drawing when user types 'e' */ + public boolean keyDown(Event e, int key) { + if ((e.id == Event.KEY_PRESS) && (key == 'e')) { + Graphics g = getGraphics(); + g.setColor(this.getBackground()); + g.fillRect(0, 0, bounds().width, bounds().height); + return true; + } + else return false; + } + + /** Respond to Button clicks: erase drawing when user clicks button */ + public boolean action(Event e, Object arg) { + if (e.target == erase_button) { + Graphics g = getGraphics(); + g.setColor(this.getBackground()); + g.fillRect(0, 0, bounds().width, bounds().height); + return true; + } + else return false; + } +} diff --git a/src/main/java/com/davidflanagan/examples/applet/Soundmap.html b/src/main/java/com/davidflanagan/examples/applet/Soundmap.html new file mode 100644 index 0000000..f2de8f9 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/Soundmap.html @@ -0,0 +1,119 @@ + + +The Soundmap Applet + + +Clicking one one of the buttons will make your browser +jump to the appropriate section of the table of contents below. +Note the use of the status line. +

+ + + + + + + + +

Java Examples in a Nutshell: Table of Contents

+
+ +
+Preface
+
+
+Part I: Core APIs
+
+Chapter 1 Java Basics
+Chapter 2 Object, Classes and Interfaces
+Chapter 3 Input/Output
+Chapter 4 Threads
+Chapter 5 Networking
+Chapter 6 Security and Cryptography
+Chapter 7 Internationalization
+Chapter 8 Reflection
+Chapter 9 Object Serialization
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Part II: Graphics and Graphical User Interfaces
+
+Chapter 10 Graphical User Interfaces
+Chapter 11 Graphics                 
+Chapter 12 Printing
+Chapter 13 Data Transfer
+Chapter 14 Java Beans
+Chapter 15 Applets
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Part III Enterprise APIs
+
+Chapter 16 Remote Method Invocation
+Chapter 17 Database access with SQL
+Chapter 18 Servlets & JSP
+Chapter 19 XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/src/main/java/com/davidflanagan/examples/applet/Soundmap.java b/src/main/java/com/davidflanagan/examples/applet/Soundmap.java new file mode 100644 index 0000000..bfa463c --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/applet/Soundmap.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.applet; +import java.applet.*; +import java.awt.*; +import java.awt.event.*; +import java.net.*; +import java.util.*; + +/** + * A Java applet that simulates a client-side imagemap. + * Plays a sound whenever the user clicks on one of the hyperlinks. + */ +public class Soundmap extends Applet implements MouseListener { + protected Image image; // The image to display. + protected Vector rects; // A list of rectangles in it. + protected AudioClip sound; // A sound to play on user clicks in a rectangle + protected ImagemapRectangle highlight; // Which rectangle is highlighted + + /** Initialize the applet */ + public void init() { + // Look up the name of the image, relative to a base URL, and load it. + // Note the use of three Applet methods in this one line. + image = this.getImage(this.getDocumentBase(), + this.getParameter("image")); + + // Lookup and parse a list of rectangular areas and their URLs. + // The convenience routine getRectangleParameter() is defined below. + rects = new Vector(); + ImagemapRectangle r; + for(int i = 0; (r = getRectangleParameter("rect" + i)) != null; i++) + rects.addElement(r); + + // Look up a sound to play when the user clicks one of those areas. + sound = this.getAudioClip(this.getDocumentBase(), + this.getParameter("sound")); + + // Specify an "event listener" object to respond to mouse button + // presses and releases. Note that this is the Java 1.1 event model. + this.addMouseListener(this); + } + + /** + * Called when the applet is being unloaded from the system. + * We use it here to "flush" the image we no longer need. This may + * result in memory and other resources being freed more quickly. + **/ + public void destroy() { image.flush(); } + + /** + * To display the applet, we simply draw the image, and highlight the + * current rectangle if any. + **/ + public void paint(Graphics g) { + g.drawImage(image, 0, 0, this); + if (highlight != null) { + g.setColor(Color.red); + g.drawRect(highlight.x, highlight.y, + highlight.width, highlight.height); + g.drawRect(highlight.x+1, highlight.y+1, + highlight.width-2, highlight.height-2); + } + } + + /** + * We override this method so that it doesn't clear the background + * before calling paint(). No clear is necessary, since paint() overwrites + * everything with an image. Causes less flickering this way. + **/ + public void update(Graphics g) { paint(g); } + + /** + * Parse a comma-separated list of rectangle coordinates and a URL. + * Used to read the imagemap rectangle definitions from applet parameters + **/ + protected ImagemapRectangle getRectangleParameter(String name) { + int x, y, w, h; + URL url; + String value = this.getParameter(name); + if (value == null) return null; + + try { + StringTokenizer st = new StringTokenizer(value, ","); + x = Integer.parseInt(st.nextToken()); + y = Integer.parseInt(st.nextToken()); + w = Integer.parseInt(st.nextToken()); + h = Integer.parseInt(st.nextToken()); + url = new URL(this.getDocumentBase(), st.nextToken()); + } + catch (NoSuchElementException e) { return null; } + catch (NumberFormatException e) { return null; } + catch (MalformedURLException e) { return null; } + + return new ImagemapRectangle(x, y, w, h, url); + } + + /** Called when a mouse button is pressed. */ + public void mousePressed(MouseEvent e) { + // On button down, check if we're inside one of the rectangles. + // If so, highlight the rectangle, display a message, and play a sound. + // The utility routine findrect() is defined below. + ImagemapRectangle r = findrect(e); + // If a rectangle is found, and is not already highlighted + if (r != null && r != highlight) { + highlight = r; // Remember which rectangle it is + showStatus("To: " + r.url); // display its URL in status line + sound.play(); // play the sound + repaint(); // request a redraw to highlight it + } + } + + /** Called when a mouse button is released. */ + public void mouseReleased(MouseEvent e) { + // If the user releases the mouse button over a highlighted + // rectangle, tell the browser to display its URL. Also, + // erase the highlight and clear status + if (highlight != null) { + ImagemapRectangle r = findrect(e); + if (r == highlight) getAppletContext().showDocument(r.url); + showStatus(""); // clear the message. + highlight = null; // forget the highlight + repaint(); // request a redraw + } + } + + /** Unused methods of the MouseListener interface */ + public void mouseEntered(MouseEvent e) {} + public void mouseExited(MouseEvent e) {} + public void mouseClicked(MouseEvent e) {} + + /** Find the rectangle we're inside. */ + protected ImagemapRectangle findrect(MouseEvent e) { + int i, x = e.getX(), y = e.getY(); + for(i = 0; i < rects.size(); i++) { + ImagemapRectangle r = (ImagemapRectangle) rects.elementAt(i); + if (r.contains(x, y)) return r; + } + return null; + } + + /** + * A helper class. Just like java.awt.Rectangle, but with a URL field. + * Note the use of a nested toplevel class for neatness. + **/ + static class ImagemapRectangle extends Rectangle { + URL url; + public ImagemapRectangle(int x, int y, int w, int h, URL url) { + super(x, y, w, h); + this.url = url; + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/applet/chirp.au b/src/main/java/com/davidflanagan/examples/applet/chirp.au new file mode 100644 index 0000000..330cb54 Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/applet/chirp.au differ diff --git a/src/main/java/com/davidflanagan/examples/applet/java_parts.gif b/src/main/java/com/davidflanagan/examples/applet/java_parts.gif new file mode 100644 index 0000000..8483c3f Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/applet/java_parts.gif differ diff --git a/src/main/java/com/davidflanagan/examples/basics/Echo.java b/src/main/java/com/davidflanagan/examples/basics/Echo.java new file mode 100644 index 0000000..231fcbe --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Echo.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; + +/** + * This program prints out all its command-line arguments. + **/ +public class Echo { + public static void main(String[] args) { + int i = 0; // Initialize the loop variable + while(i < args.length) { // Loop until the end of array + System.out.print(args[i] + " "); // Print each argument out + i++; // Increment the loop variable + } + System.out.println(); // Terminate the line + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/FactComputer.java b/src/main/java/com/davidflanagan/examples/basics/FactComputer.java new file mode 100644 index 0000000..ca2d36e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/FactComputer.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; + +/** + * This program computes and displays the factorial of a number specified + * on the command line. It handles possible user input errors with try/catch. + **/ +public class FactComputer { + public static void main(String[] args) { + // Try to compute a factorial. + // If something goes wrong, handle it in the catch clause below. + try { + int x = Integer.parseInt(args[0]); + System.out.println(x + "! = " + Factorial4.factorial(x)); + } + // The user forgot to specify an argument. + // Thrown if args[0] is undefined. + catch (ArrayIndexOutOfBoundsException e) { + System.out.println("You must specify an argument"); + System.out.println("Usage: java FactComputer "); + } + // The argument is not a number. Thrown by Integer.parseInt(). + catch (NumberFormatException e) { + System.out.println("The argument you specify must be an integer"); + } + // The argument is < 0. Thrown by Factorial4.factorial() + catch (IllegalArgumentException e) { + // Display the message sent by the factorial() method: + System.out.println("Bad argument: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/FactQuoter.java b/src/main/java/com/davidflanagan/examples/basics/FactQuoter.java new file mode 100644 index 0000000..a0a1d17 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/FactQuoter.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; +import java.io.*; // Import all classes in java.io package. Saves typing. + +/** + * This program displays factorials as the user enters values interactively + **/ +public class FactQuoter { + public static void main(String[] args) throws IOException { + // This is how we set things up to read lines of text from the user. + BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); + // Loop forever + for(;;) { + // Display a prompt to the user + System.out.print("FactQuoter> "); + // Read a line from the user + String line = in.readLine(); + // If we reach the end-of-file, + // or if the user types "quit", then quit + if ((line == null) || line.equals("quit")) break; + // Try to parse the line, and compute and print the factorial + try { + int x = Integer.parseInt(line); + System.out.println(x + "! = " + Factorial4.factorial(x)); + } + // If anything goes wrong, display a generic error message + catch(Exception e) { System.out.println("Invalid Input"); } + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/Factorial.java b/src/main/java/com/davidflanagan/examples/basics/Factorial.java new file mode 100644 index 0000000..a3e4a04 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Factorial.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; +/** + * This class doesn't define a main() method, so it isn't a program by itself. + * It does define a useful method that we can use in other programs, though. + **/ +public class Factorial { + /** Compute and return x!, the factorial of x */ + public static int factorial(int x) { + if (x < 0) throw new IllegalArgumentException("x must be >= 0"); + int fact = 1; + for(int i = 2; i <= x; i++) // loop + fact *= i; // shorthand for: fact = fact * i; + return fact; + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/Factorial2.java b/src/main/java/com/davidflanagan/examples/basics/Factorial2.java new file mode 100644 index 0000000..d0dac0d --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Factorial2.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; +/** + * This class shows a recursive method to compute factorials. This method + * calls itself repeatedly based on the formula: n! = n * (n-1)! + **/ +public class Factorial2 { + public static long factorial(long x) { + if (x < 0) throw new IllegalArgumentException("x must be >= 0"); + if (x <= 1) return 1; // Stop recursing here + else return x * factorial(x-1); // Recurse by calling ourselves + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/Factorial3.java b/src/main/java/com/davidflanagan/examples/basics/Factorial3.java new file mode 100644 index 0000000..c3992af --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Factorial3.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; + +/** + * This class computes factorials and caches the results in a table for reuse. + * 20! is as high as we can go using the long data type, so check the argument + * passed and "throw an exception" if it is too big or too small. + **/ +public class Factorial3 { + // Create an array to cache values 0! through 20!. + static long[] table = new long[21]; + // A "static initializer": initialize the first value in the array + static { table[0] = 1; } // factorial of 0 is 1. + // Remember the highest initialized value in the array + static int last = 0; + + public static long factorial(int x) throws IllegalArgumentException { + // Check if x is too big or too small. Throw an exception if so. + if (x >= table.length) // ".length" returns length of any array + throw new IllegalArgumentException("Overflow; x is too large."); + if (x<0) throw new IllegalArgumentException("x must be non-negative."); + + // Compute and cache any values that are not yet cached. + while(last < x) { + table[last + 1] = table[last] * (last + 1); + last++; + } + // Now return the cached factorial of x. + return table[x]; + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/Factorial4.java b/src/main/java/com/davidflanagan/examples/basics/Factorial4.java new file mode 100644 index 0000000..5caa347 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Factorial4.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; + +// Import some other classes we'll use in this example. +// Once we import a class, we don't have to type its full name. +import java.math.BigInteger; // Import BigInteger from java.math package +import java.util.*; // Import all classes (including ArrayList) from java.util + +/** + * This version of the program uses arbitrary precision integers, so it does + * not have an upper-bound on the values it can compute. It uses an ArrayList + * object to cache computed values instead of a fixed-size array. An ArrayList + * is like an array, but can grow to any size. The factorial() method is + * declared "synchronized" so that it can be safely used in multi-threaded + * programs. Look up java.math.BigInteger and java.util.ArrayList while + * studying this class. Prior to Java 1.2, use Vector instead of ArrayList + **/ +public class Factorial4 { + protected static ArrayList table = new ArrayList(); // create cache + static { // Initialize the first element of the cache with !0 = 1. + table.add(BigInteger.valueOf(1)); + } + + /** The factorial() method, using BigIntegers cached in a ArrayList */ + public static synchronized BigInteger factorial(int x) { + if (x<0) throw new IllegalArgumentException("x must be non-negative."); + for(int size = table.size(); size <= x; size++) { + BigInteger lastfact = (BigInteger)table.get(size-1); + BigInteger nextfact = lastfact.multiply(BigInteger.valueOf(size)); + table.add(nextfact); + } + return (BigInteger) table.get(x); + } + + /** + * A simple main() method that we can use as a standalone test program + * for our factorial() method. + **/ + public static void main(String[] args) { + for(int i = 0; i <= 50; i++) + System.out.println(i + "! = " + factorial(i)); + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/Fibonacci.java b/src/main/java/com/davidflanagan/examples/basics/Fibonacci.java new file mode 100644 index 0000000..decd319 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Fibonacci.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; +/** + * This program prints out the first 20 numbers in the Fibonacci sequence. + * Each term is formed by adding together the previous two terms in the + * sequence, starting with the terms 1 and 1. + **/ +public class Fibonacci { + public static void main(String[] args) { + int n0 = 1, n1 = 1, n2; // Initialize variables + System.out.print(n0 + " " + // Print first and second terms + n1 + " "); // of the series + + for(int i = 0; i < 18; i++) { // Loop for the next 18 terms + n2 = n1 + n0; // Next term is sum of previous two + System.out.print(n2 + " "); // Print it out + n0 = n1; // First previous becomes 2nd previous + n1 = n2; // And current number becomes previous + } + System.out.println(); // Terminate the line + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/FizzBuzz.java b/src/main/java/com/davidflanagan/examples/basics/FizzBuzz.java new file mode 100644 index 0000000..8317ecf --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/FizzBuzz.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; + +/** + * This program plays the game "Fizzbuzz". It counts to 100, replacing each + * multiple of 5 with the word "fizz", each multiple of 7 with the word "buzz", + * and each multiple of both with the word "fizzbuzz". It uses the modulo + * operator (%) to determine if a number is divisible by another. + **/ +public class FizzBuzz { // Everything in Java is a class + public static void main(String[] args) { // Every program must have main() + for(int i = 1; i <= 100; i++) { // count from 1 to 100 + if (((i % 5) == 0)&& ((i % 7) == 0)) // Is it a multiple of 5 & 7? + System.out.print("fizzbuzz"); + else if ((i % 5) == 0) // Is it a multiple of 5? + System.out.print("fizz"); + else if ((i % 7) == 0) // Is it a multiple of 7? + System.out.print("buzz"); + else System.out.print(i); // Not a multiple of 5 or 7 + System.out.print(" "); + } + System.out.println(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/FizzBuzz2.java b/src/main/java/com/davidflanagan/examples/basics/FizzBuzz2.java new file mode 100644 index 0000000..4fe2dc4 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/FizzBuzz2.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; + +/** + * This class is much like the FizzBuzz class, but uses a switch statement + * instead of repeated if/else statements + **/ +public class FizzBuzz2 { + public static void main(String[] args) { + for(int i = 1; i <= 100; i++) { // count from 1 to 100 + switch(i % 35) { // What's the remainder when divided by 35? + case 0: // For multiples of 35... + System.out.print("fizzbuzz "); // print "fizzbuzz". + break; // Don't forget this statement! + case 5: case 10: case 15: // If the remainder is any of these + case 20: case 25: case 30: // then the number is a multiple of 5 + System.out.print("fizz "); // so print "fizz". + break; + case 7: case 14: case 21: case 28: // For any multiple of 7... + System.out.print("buzz "); // print "buzz". + break; + default: // For any other number... + System.out.print(i + " "); // print the number. + break; + } + } + System.out.println(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/Hello.java b/src/main/java/com/davidflanagan/examples/basics/Hello.java new file mode 100644 index 0000000..1b9570b --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Hello.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; // A unique class name prefix +public class Hello { // Everything in Java is a class + public static void main(String[] args) { // All programs must have main() + System.out.println("Hello World!"); // Say hello! + } // This marks the end of main() +} // Marks the end of the class diff --git a/src/main/java/com/davidflanagan/examples/basics/Reverse.java b/src/main/java/com/davidflanagan/examples/basics/Reverse.java new file mode 100644 index 0000000..40aebd3 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Reverse.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; + +/** + * This program echos the command-line arguments backwards. + **/ +public class Reverse { + public static void main(String[] args) { + // Loop backwards through the array of arguments + for(int i = args.length-1; i >= 0; i--) { + // Loop backwards through the characters in each argument + for(int j=args[i].length()-1; j>=0; j--) { + // Print out character j of argument i. + System.out.print(args[i].charAt(j)); + } + System.out.print(" "); // Add a space at the end of each argument. + } + System.out.println(); // And terminate the line when we're done. + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/Rot13Input.java b/src/main/java/com/davidflanagan/examples/basics/Rot13Input.java new file mode 100644 index 0000000..ddeb720 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Rot13Input.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; +import java.io.*; // We're doing input, so import I/O classes + +/** + * This program reads lines of text from the user, encodes them using the + * trivial "Rot13" substitution cipher, and then prints out the encoded lines. + **/ +public class Rot13Input { + public static void main(String[] args) throws IOException { + // Get set up to read lines of text from the user + BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); + for(;;) { // Loop forever + System.out.print("> "); // Print a prompt + String line = in.readLine(); // Read a line + if ((line == null) || line.equals("quit")) // If EOF or "quit"... + break; // ...break out of loop + StringBuffer buf = new StringBuffer(line); // Use a StringBuffer + for(int i = 0; i < buf.length(); i++) // For each character... + buf.setCharAt(i, rot13(buf.charAt(i)));// ..read, encode, store + System.out.println(buf); // Print encoded line + } + } + + /** + * This method performs the Rot13 substitution cipher. It "rotates" + * each letter 13 places through the alphabet. Since the Latin alphabet + * has 26 letters, this method both encodes and decodes. + **/ + public static char rot13(char c) { + if ((c >= 'A') && (c <= 'Z')) { // For uppercase letters + c += 13; // Rotate forward 13 + if (c > 'Z') c -= 26; // And subtract 26 if necessary + } + if ((c >= 'a') && (c <= 'z')) { // Do the same for lowercase letters + c += 13; + if (c > 'z') c -= 26; + } + return c; // Return the modified letter + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/Sieve.java b/src/main/java/com/davidflanagan/examples/basics/Sieve.java new file mode 100644 index 0000000..b2feb3b --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/Sieve.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; + +/** + * This program computes prime numbers using the Sieve of Eratosthenes + * algorithm: rule out multiples of all lower prime numbers, and anything + * remaining is a prime. It prints out the largest prime number less than + * or equal to the supplied command-line argument. + **/ +public class Sieve { + public static void main(String[] args) { + // We will compute all primes less than the value specified on the + // command line, or, if no argument, all primes less than 100. + int max = 100; // Assign a default value + try { max = Integer.parseInt(args[0]); } // Parse user-supplied arg + catch (Exception e) {} // Silently ignore exceptions. + + // Create an array that specifies whether each number is prime or not. + boolean[] isprime = new boolean[max+1]; + + // Assume that all numbers are primes, until proven otherwise. + for(int i = 0; i <= max; i++) isprime[i] = true; + + // However, we know that 0 and 1 are not primes. Make a note of it. + isprime[0] = isprime[1] = false; + + // To compute all primes less than max, we need to rule out + // multiples of all integers less than the square root of max. + int n = (int) Math.ceil(Math.sqrt(max)); // See java.lang.Math class + + // Now, for each integer i from 0 to n: + // If i is a prime, then none of its multiples are primes, + // so indicate this in the array. If i is not a prime, then + // its multiples have already been ruled out by one of the + // prime factors of i, so we can skip this case. + for(int i = 0; i <= n; i++) { + if (isprime[i]) // If i is a prime, + for(int j = 2*i; j <= max; j = j + i) // loop through multiples + isprime[j] = false; // they are not prime. + } + + // Now go look for the largest prime: + int largest; + for(largest = max; !isprime[largest]; largest--) ; // empty loop body + + // Output the result + System.out.println("The largest prime less than or equal to " + max + + " is " + largest); + } +} diff --git a/src/main/java/com/davidflanagan/examples/basics/SortNumbers.java b/src/main/java/com/davidflanagan/examples/basics/SortNumbers.java new file mode 100644 index 0000000..685534d --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/basics/SortNumbers.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.basics; + +/** + * This class demonstrates how to sort numbers using a simple algorithm + **/ +public class SortNumbers { + /** + * This is a very simple sorting algorithm that is not very efficient + * when sorting large numbers of things + **/ + public static void sort(double[] nums) { + // Loop through each element of the array, sorting as we go. + // Each time through, find the smallest remaining element, and move it + // to the first unsorted position in the array. + for(int i = 0; i < nums.length; i++) { + int min = i; // holds the index of the smallest element + // find the smallest one between i and the end of the array + for(int j = i; j < nums.length; j++) { + if (nums[j] < nums[min]) min = j; + } + // Now swap the smallest one with element i. + // This leaves all elements between 0 and i sorted. + double tmp; + tmp = nums[i]; + nums[i] = nums[min]; + nums[min] = tmp; + } + } + + /** This is a simple test program for the algorithm above */ + public static void main(String[] args) { + double[] nums = new double[10]; // Create an array to hold numbers + for(int i = 0; i < nums.length; i++) // Generate random numbers + nums[i] = Math.random() * 100; + sort(nums); // Sort them + for(int i = 0; i < nums.length; i++) // Print them out + System.out.println(nums[i]); + } +} diff --git a/src/main/java/com/davidflanagan/examples/beans/Alignment.java b/src/main/java/com/davidflanagan/examples/beans/Alignment.java new file mode 100644 index 0000000..72b8be0 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/Alignment.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.beans; + +/** This class defines an enumerated type with three values */ +public class Alignment { + /** This private constructor prevents anyone from instantiating us */ + private Alignment() {}; + // The following three constants are the only instances of this class + public static final Alignment LEFT = new Alignment(); + public static final Alignment CENTER = new Alignment(); + public static final Alignment RIGHT = new Alignment(); +} diff --git a/src/main/java/com/davidflanagan/examples/beans/AlignmentEditor.java b/src/main/java/com/davidflanagan/examples/beans/AlignmentEditor.java new file mode 100644 index 0000000..6fac3b5 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/AlignmentEditor.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.beans; +import java.beans.*; +import java.awt.*; + +/** + * This PropertyEditor defines the enumerated values of the alignment property + * so that a bean box or IDE can present those values to the user for selection + **/ +public class AlignmentEditor extends PropertyEditorSupport { + /** Return the list of value names for the enumerated type. */ + public String[] getTags() { + return new String[] { "left", "center", "right" }; + } + + /** Convert each of those value names into the actual value. */ + public void setAsText(String s) { + if (s.equals("left")) setValue(Alignment.LEFT); + else if (s.equals("center")) setValue(Alignment.CENTER); + else if (s.equals("right")) setValue(Alignment.RIGHT); + else throw new IllegalArgumentException(s); + } + + /** This is an important method for code generation. */ + public String getJavaInitializationString() { + Object o = getValue(); + if (o == Alignment.LEFT) + return "com.davidflanagan.examples.beans.Alignment.LEFT"; + if (o == Alignment.CENTER) + return "com.davidflanagan.examples.beans.Alignment.CENTER"; + if (o == Alignment.RIGHT) + return "com.davidflanagan.examples.beans.Alignment.RIGHT"; + return null; + } +} diff --git a/src/main/java/com/davidflanagan/examples/beans/AnswerEvent.java b/src/main/java/com/davidflanagan/examples/beans/AnswerEvent.java new file mode 100644 index 0000000..389efe7 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/AnswerEvent.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.beans; + +/** + * The YesNoPanel class fires an event of this type when the user clicks one + * of its buttons. The id field specifies which button the user pressed. + **/ +public class AnswerEvent extends java.util.EventObject { + public static final int YES = 0, NO = 1, CANCEL = 2; // Button constants + protected int id; // Which button was pressed? + public AnswerEvent(Object source, int id) { + super(source); + this.id = id; + } + public int getID() { return id; } // Return the button +} diff --git a/src/main/java/com/davidflanagan/examples/beans/AnswerListener.java b/src/main/java/com/davidflanagan/examples/beans/AnswerListener.java new file mode 100644 index 0000000..154cddd --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/AnswerListener.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.beans; + +/** + * Classes that want to be notified when the user clicks a button in a + * YesNoPanel should implement this interface. The method invoked depends + * on which button the user clicked. + **/ +public interface AnswerListener extends java.util.EventListener { + public void yes(AnswerEvent e); + public void no(AnswerEvent e); + public void cancel(AnswerEvent e); +} diff --git a/src/main/java/com/davidflanagan/examples/beans/MultiLineLabel.java b/src/main/java/com/davidflanagan/examples/beans/MultiLineLabel.java new file mode 100644 index 0000000..beb0291 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/MultiLineLabel.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.beans; +import java.awt.*; +import java.util.*; + +/** + * A custom component that displays multiple lines of text with specified + * margins and alignment. In Java 1.1 we could also subclass Component, + * making this a "lightweight" component. Instead, we try to maintain + * Java 1.0 compatibility for this component. This means that you will see + * deprecation warnings when you compile this class with Java 1.1 or later. + **/ +public class MultiLineLabel extends Canvas { + // User-specified properties + protected String label; // The label, not broken into lines + protected int margin_width; // Left and right margins + protected int margin_height; // Top and bottom margins + protected Alignment alignment; // The alignment of the text. + + // Computed state values + protected int num_lines; // The number of lines + protected String[] lines; // The label, broken into lines + protected int[] line_widths; // How wide each line is + protected int max_width; // The width of the widest line + protected int line_height; // Total height of the font + protected int line_ascent; // Font height above baseline + protected boolean measured = false; // Have the lines been measured? + + // Here are five versions of the constructor. + public MultiLineLabel(String label, int margin_width, + int margin_height, Alignment alignment) { + this.label = label; // Remember all the properties. + this.margin_width = margin_width; + this.margin_height = margin_height; + this.alignment = alignment; + newLabel(); // Break the label up into lines. + } + + public MultiLineLabel(String label, int margin_width, int margin_height) { + this(label, margin_width, margin_height, Alignment.LEFT); + } + + public MultiLineLabel(String label, Alignment alignment) { + this(label, 10, 10, alignment); + } + + public MultiLineLabel(String label) { this(label, 10, 10, Alignment.LEFT);} + + public MultiLineLabel() { this(""); } + + // Methods to set and query the various attributes of the component. + // Note that some query methods are inherited from the superclass. + public void setLabel(String label) { + this.label = label; + newLabel(); // Break the label into lines. + measured = false; // Note that we need to measure lines. + repaint(); // Request a redraw. + } + + public void setFont(Font f) { + super.setFont(f); // Tell our superclass about the new font. + measured = false; // Note that we need to remeasure lines. + repaint(); // Request a redraw. + } + + public void setForeground(Color c) { + super.setForeground(c); // Tell our superclass about the new color. + repaint(); // Request a redraw (size is unchanged). + } + + public void setAlignment(Alignment a) { alignment = a; repaint(); } + public void setMarginWidth(int mw) { margin_width = mw; repaint(); } + public void setMarginHeight(int mh) { margin_height = mh; repaint(); } + + // Property getter methods. Note that getFont(), getForeground(), etc. + // are inherited from the superclass. + public String getLabel() { return label; } + public Alignment getAlignment() { return alignment; } + public int getMarginWidth() { return margin_width; } + public int getMarginHeight() { return margin_height; } + + /** + * This method is called by a layout manager when it wants to + * know how big we'd like to be. In Java 1.1, getPreferredSize() is + * the preferred version of this method. We use this deprecated version + * so that this component can interoperate with 1.0 components. + */ + public Dimension preferredSize() { + if (!measured) measure(); + return new Dimension(max_width + 2*margin_width, + num_lines * line_height + 2*margin_height); + } + + /** + * This method is called when the layout manager wants to know + * the bare minimum amount of space we need to get by. + * For Java 1.1, we'd use getMinimumSize(). + */ + public Dimension minimumSize() { return preferredSize(); } + + /** + * This method draws the component. + * Note that it handles the margins and the alignment, but that + * it doesn't have to worry about the color or font--the superclass + * takes care of setting those in the Graphics object we're passed. + **/ + public void paint(Graphics g) { + int x, y; + Dimension size = this.size(); // use getSize() in Java 1.1 + if (!measured) measure(); + y = line_ascent + (size.height - num_lines * line_height)/2; + for(int i = 0; i < num_lines; i++, y += line_height) { + if (alignment == Alignment.LEFT) x = margin_width; + else if (alignment == Alignment.CENTER) + x = (size.width - line_widths[i])/2; + else x = size.width - margin_width - line_widths[i]; + g.drawString(lines[i], x, y); + } + } + + /** + * This internal method breaks a specified label up into an array of lines. + * It uses the StringTokenizer utility class. + **/ + protected synchronized void newLabel() { + StringTokenizer t = new StringTokenizer(label, "\n"); + num_lines = t.countTokens(); + lines = new String[num_lines]; + line_widths = new int[num_lines]; + for(int i = 0; i < num_lines; i++) lines[i] = t.nextToken(); + } + + /** + * This internal method figures out how the font is, and how wide each + * line of the label is, and how wide the widest line is. + **/ + protected synchronized void measure() { + FontMetrics fm = this.getToolkit().getFontMetrics(this.getFont()); + line_height = fm.getHeight(); + line_ascent = fm.getAscent(); + max_width = 0; + for(int i = 0; i < num_lines; i++) { + line_widths[i] = fm.stringWidth(lines[i]); + if (line_widths[i] > max_width) max_width = line_widths[i]; + } + measured = true; + } +} diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanel.java b/src/main/java/com/davidflanagan/examples/beans/YesNoPanel.java new file mode 100644 index 0000000..74564b2 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/YesNoPanel.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.beans; +import java.awt.*; +import java.awt.event.*; +import java.util.*; + +/** + * This JavaBean displays a multi-line message and up to three buttons. It + * fires an AnswerEvent when the user clicks on one of the buttons + **/ +public class YesNoPanel extends Panel { + // Properties of the bean. + protected String messageText; // The message to display + protected Alignment alignment; // The alignment of the message + protected String yesLabel; // Text for the yes, no, & cancel buttons + protected String noLabel; + protected String cancelLabel; + + // Internal components of the panel + protected MultiLineLabel message; + protected Button yes, no, cancel; + + /** The no-argument bean constructor, with default property values */ + public YesNoPanel() { this("Your\nMessage\nHere"); } + + public YesNoPanel(String messageText) { + this(messageText, Alignment.LEFT, "Yes", "No", "Cancel"); + } + + /** A constructor for programmers using this class "by hand" */ + public YesNoPanel(String messageText, Alignment alignment, + String yesLabel, String noLabel, String cancelLabel) + { + // Create the components for this panel + setLayout(new BorderLayout(15, 15)); + + // Put the message label in the middle of the window. + message = new MultiLineLabel(messageText, 20, 20, alignment); + add(message, BorderLayout.CENTER); + + // Create a panel for the Panel buttons and put it at the bottom + // of the Panel. Specify a FlowLayout layout manager for it. + Panel buttonbox = new Panel(); + buttonbox.setLayout(new FlowLayout(FlowLayout.CENTER, 25, 15)); + add(buttonbox, BorderLayout.SOUTH); + + // Create each specified button, specifying the action listener + // and action command for each, and adding them to the buttonbox + yes = new Button(); // Create buttons + no = new Button(); + cancel = new Button(); + // Add the buttons to the button box + buttonbox.add(yes); + buttonbox.add(no); + buttonbox.add(cancel); + + // Register listeners for each button + yes.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + fireEvent(new AnswerEvent(YesNoPanel.this, + AnswerEvent.YES)); + } + }); + + no.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + fireEvent(new AnswerEvent(YesNoPanel.this, + AnswerEvent.NO)); + } + }); + cancel.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + fireEvent(new AnswerEvent(YesNoPanel.this, + AnswerEvent.CANCEL)); + } + }); + + // Now call property setter methods to set the message and button + // components to contain the right text + setMessageText(messageText); + setAlignment(alignment); + setYesLabel(yesLabel); + setNoLabel(noLabel); + setCancelLabel(cancelLabel); + } + + // Methods to query all of the bean properties. + public String getMessageText() { return messageText; } + public Alignment getAlignment() { return alignment; } + public String getYesLabel() { return yesLabel; } + public String getNoLabel() { return noLabel; } + public String getCancelLabel() { return cancelLabel; } + + // Methods to set all of the bean properties. + public void setMessageText(String messageText) { + this.messageText = messageText; + message.setLabel(messageText); + validate(); + } + + public void setAlignment(Alignment alignment) { + this.alignment = alignment; + message.setAlignment(alignment); + } + + public void setYesLabel(String l) { + yesLabel = l; + yes.setLabel(l); + yes.setVisible((l != null) && (l.length() > 0)); + validate(); + } + + public void setNoLabel(String l) { + noLabel = l; + no.setLabel(l); + no.setVisible((l != null) && (l.length() > 0)); + validate(); + } + + public void setCancelLabel(String l) { + cancelLabel = l; + cancel.setLabel(l); + cancel.setVisible((l != null) && (l.length() > 0)); + validate(); + } + + public void setFont(Font f) { + super.setFont(f); // Invoke the superclass method + message.setFont(f); + yes.setFont(f); + no.setFont(f); + cancel.setFont(f); + validate(); + } + + /** This field holds a list of registered ActionListeners. */ + protected Vector listeners = new Vector(); + + /** Register an action listener to be notified when a button is pressed */ + public void addAnswerListener(AnswerListener l) { + listeners.addElement(l); + } + + /** Remove an Answer listener from our list of interested listeners */ + public void removeAnswerListener(AnswerListener l) { + listeners.removeElement(l); + } + + /** Send an event to all registered listeners */ + public void fireEvent(AnswerEvent e) { + // Make a copy of the list and fire the events using that copy. + // This means that listeners can be added or removed from the original + // list in response to this event. We ought to be able to just use an + // enumeration for the vector, but that doesn't actually copy the list. + Vector list = (Vector) listeners.clone(); + for(int i = 0; i < list.size(); i++) { + AnswerListener listener = (AnswerListener)list.elementAt(i); + switch(e.getID()) { + case AnswerEvent.YES: listener.yes(e); break; + case AnswerEvent.NO: listener.no(e); break; + case AnswerEvent.CANCEL: listener.cancel(e); break; + } + } + } + + /** A main method that demonstrates the class */ + public static void main(String[] args) { + // Create an instance of InfoPanel, with title and message specified: + YesNoPanel p = new YesNoPanel("Do you really want to quit?"); + + // Register an action listener for the Panel. This one just prints + // the results out to the console. + p.addAnswerListener(new AnswerListener() { + public void yes(AnswerEvent e) { System.exit(0); } + public void no(AnswerEvent e) { System.out.println("No"); } + public void cancel(AnswerEvent e) { + System.out.println("Cancel"); + } + }); + + Frame f = new Frame(); + f.add(p); + f.pack(); + f.setVisible(true); + } +} diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanelBeanInfo.java b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelBeanInfo.java new file mode 100644 index 0000000..7be3e62 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelBeanInfo.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.beans; +import java.beans.*; +import java.lang.reflect.*; +import java.awt.*; + +/** + * This BeanInfo class provides additional information about the YesNoPanel + * bean in addition to what can be obtained through introspection alone. + **/ +public class YesNoPanelBeanInfo extends SimpleBeanInfo { + /** + * Return an icon for the bean. We should really check the kind argument + * to see what size icon the beanbox wants, but since we only have one + * icon to offer, we just return it and let the beanbox deal with it + **/ + public Image getIcon(int kind) { return loadImage("YesNoPanelIcon.gif"); } + + /** + * Return a descriptor for the bean itself. It specifies a customizer + * for the bean class. We could also add a description string here + **/ + public BeanDescriptor getBeanDescriptor() { + return new BeanDescriptor(YesNoPanel.class, + YesNoPanelCustomizer.class); + } + + /** This is a convenience method for creating PropertyDescriptor objects */ + static PropertyDescriptor prop(String name, String description) { + try { + PropertyDescriptor p = + new PropertyDescriptor(name, YesNoPanel.class); + p.setShortDescription(description); + return p; + } + catch(IntrospectionException e) { return null; } + } + + // Initialize a static array of PropertyDescriptor objects that provide + // additional information about the properties supported by the bean. + // By explicitly specifying property descriptors, we are able to provide + // simple help strings for each property; these would not be available to + // the beanbox through simple introspection. We are also able to register + // a special property editors for the messageText property + static PropertyDescriptor[] props = { + prop("messageText", "The message text that appears in the bean body"), + prop("alignment", "The alignment of the message text"), + prop("yesLabel", "The label for the Yes button"), + prop("noLabel", "The label for the No button"), + prop("cancelLabel","The label for the Cancel button"), + prop("font", "The font for the message and buttons"), + prop("background", "The background color"), + prop("foreground", "The foreground color"), + }; + static { + props[0].setPropertyEditorClass(YesNoPanelMessageEditor.class); + } + + /** Return the property descriptors for this bean */ + public PropertyDescriptor[] getPropertyDescriptors() { return props; } + + /** The message property is most often customized; make it the default */ + public int getDefaultPropertyIndex() { return 0; } +} diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanelCustomizer.java b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelCustomizer.java new file mode 100644 index 0000000..eada22b --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelCustomizer.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.beans; +import java.awt.*; +import java.awt.event.*; +import java.beans.*; + +/** + * This class is a customizer for the YesNoPanel bean. It displays a + * TextArea and three TextFields where the user can enter the main message + * and the labels for each of the three buttons. It does not allow the + * alignment property to be set. + **/ +public class YesNoPanelCustomizer extends Panel + implements Customizer, TextListener +{ + protected YesNoPanel bean; // The bean being customized + protected TextArea message; // For entering the message + protected TextField fields[]; // For entering button text + + // The bean box calls this method to tell us what object to customize. + // This method will always be called before the customizer is displayed, + // so it is safe to create the customizer GUI here. + public void setObject(Object o) { + bean = (YesNoPanel)o; // save the object we're customizing + + // Put a label at the top of the panel. + this.setLayout(new BorderLayout()); + this.add(new Label("Enter the message to appear in the panel:"), + "North"); + + // And a big text area below it for entering the message. + message = new TextArea(bean.getMessageText()); + message.addTextListener(this); + // TextAreas don't know how big they want to be. You must tell them. + message.setSize(400, 200); + this.add(message, "Center"); + + // Then add a row of textfields for entering the button labels. + Panel buttonbox = new Panel(); // The row container + buttonbox.setLayout(new GridLayout(1, 0, 25, 10)); // Equally spaced + this.add(buttonbox, "South"); // Put row on bottom + + // Now go create three TextFields to put in this row. But actually + // position a Label above each, so create an container for each + // TextField+Label combination. + fields = new TextField[3]; // Array of TextFields. + String[] labels = new String[] { // Labels for each. + "Yes Button Label", "No Button Label", "Cancel Button Label"}; + String[] values = new String[] { // Initial values of each. + bean.getYesLabel(), bean.getNoLabel(), bean.getCancelLabel()}; + for(int i = 0; i < 3; i++) { + Panel p = new Panel(); // Create a container. + p.setLayout(new BorderLayout()); // Give it a BorderLayout. + p.add(new Label(labels[i]), "North"); // Put a label on the top. + fields[i] = new TextField(values[i]); // Create the text field. + p.add(fields[i], "Center"); // Put it below the label. + fields[i].addTextListener(this); // Set the event listener. + buttonbox.add(p); // Add container to row. + } + } + // Add some space around the outside of the panel. + public Insets getInsets() { return new Insets(10, 10, 10, 10); } + + // This is the method defined by the TextListener interface. Whenever the + // user types a character in the TextArea or TextFields, this will get + // called. It updates the appropriate property of the bean and fires a + // property changed event, as all customizers are required to do. + // Note that we are not required to fire an event for every keystroke. + // Instead we could include an "Apply" button that would make all the + // changes at once, with a single property changed event. + public void textValueChanged(TextEvent e) { + TextComponent t = (TextComponent)e.getSource(); + String s = t.getText(); + if (t == message) bean.setMessageText(s); + else if (t == fields[0]) bean.setYesLabel(s); + else if (t == fields[1]) bean.setNoLabel(s); + else if (t == fields[2]) bean.setCancelLabel(s); + listeners.firePropertyChange(null, null, null); + } + + // This code uses the PropertyChangeSupport class to maintain a list of + // listeners interested in the edits we make to the bean. + protected PropertyChangeSupport listeners =new PropertyChangeSupport(this); + public void addPropertyChangeListener(PropertyChangeListener l) { + listeners.addPropertyChangeListener(l); + } + public void removePropertyChangeListener(PropertyChangeListener l) { + listeners.removePropertyChangeListener(l); + } +} diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanelIcon.gif b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelIcon.gif new file mode 100644 index 0000000..b455f3e Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelIcon.gif differ diff --git a/src/main/java/com/davidflanagan/examples/beans/YesNoPanelMessageEditor.java b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelMessageEditor.java new file mode 100644 index 0000000..8e11d0e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/YesNoPanelMessageEditor.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.beans; +import java.beans.*; +import java.awt.*; +import java.awt.event.*; + +/** + * This class is a custom editor for the messageText property of the + * YesNoPanel bean. It is necessary because the default editor for + * properties of type String does not allow multi-line strings + * to be entered. + */ +public class YesNoPanelMessageEditor implements PropertyEditor { + protected String value; // The value we will be editing. + + public void setValue(Object o) { value = (String) o; } + public Object getValue() { return value; } + public void setAsText(String s) { value = s; } + public String getAsText() { return value; } + public String[] getTags() { return null; } // not enumerated; no tags + + // Say that we allow custom editing. + public boolean supportsCustomEditor() { return true; } + + // Return the custom editor. This just creates and returns a TextArea + // to edit the multi-line text. But it also registers a listener on the + // text area to update the value as the user types and to fire the + // property change events that property editors are required to fire. + public Component getCustomEditor() { + final TextArea t = new TextArea(value); + t.setSize(300, 150); // TextArea has no preferred size, so set one + t.addTextListener(new TextListener() { + public void textValueChanged(TextEvent e) { + value = t.getText(); + listeners.firePropertyChange(null, null, null); + } + }); + return t; + } + + // Visual display of the value, for use with the custom editor. + // Just print some instructions and hope they fit in the in the box. + // This could be more sophisticated. + public boolean isPaintable() { return true; } + public void paintValue(Graphics g, Rectangle r) { + g.setClip(r); + g.drawString("Click to edit...", r.x+5, r.y+15); + } + + // Important method for code generators. Note that it really ought to + // escape any quotes or backslashes in value before returning the string. + public String getJavaInitializationString() { return "\"" + value + "\""; } + + // This code uses the PropertyChangeSupport class to maintain a list of + // listeners interested in the edits we make to the value. + protected PropertyChangeSupport listeners =new PropertyChangeSupport(this); + public void addPropertyChangeListener(PropertyChangeListener l) { + listeners.addPropertyChangeListener(l); + } + public void removePropertyChangeListener(PropertyChangeListener l) { + listeners.removePropertyChangeListener(l); + } +} diff --git a/src/main/java/com/davidflanagan/examples/beans/makejar.sh b/src/main/java/com/davidflanagan/examples/beans/makejar.sh new file mode 100644 index 0000000..5fe4b7c --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/makejar.sh @@ -0,0 +1 @@ +jar cmf manifest.stub YesNoPanelBean.jar -C ../../../../ com/davidflanagan/examples/beans/ diff --git a/src/main/java/com/davidflanagan/examples/beans/manifest.stub b/src/main/java/com/davidflanagan/examples/beans/manifest.stub new file mode 100644 index 0000000..b2bcb56 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/beans/manifest.stub @@ -0,0 +1,5 @@ +Name: com/davidflanagan/examples/beans/MultiLineLabel.class +Java-Bean: true + +Name: com/davidflanagan/examples/beans/YesNoPanel.class +Java-Bean: true diff --git a/src/main/java/com/davidflanagan/examples/classes/Averager.java b/src/main/java/com/davidflanagan/examples/classes/Averager.java new file mode 100644 index 0000000..8376e42 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/classes/Averager.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.classes; +/** + * A class to compute the running average of numbers passed to it + **/ +public class Averager { + // Private fields to hold the current state. + private int n = 0; + private double sum = 0.0, sumOfSquares = 0.0; + + /** + * This method adds a new datum into the average. + **/ + public void addDatum(double x) { + n++; + sum += x; + sumOfSquares += x * x; + } + + /** This method returns the average of all numbers passed to addDatum() */ + public double getAverage() { return sum / n; } + + /** This method returns the standard deviation of the data */ + public double getStandardDeviation() { + return Math.sqrt(((sumOfSquares - sum*sum/n)/n)); + } + + /** This method returns the number of numbers passed to addDatum() */ + public double getNum() { return n; } + + /** This method returns the sum of all numbers passed to addDatum() */ + public double getSum() { return sum; } + + /** This method returns the sum of the squares of all numbers. */ + public double getSumOfSquares() { return sumOfSquares; } + + /** This method resets the Averager object to begin from scratch */ + public void reset() { n = 0; sum = 0.0; sumOfSquares = 0.0; } + + /** + * This nested class is a simple test program we can use to check that + * our code works okay. + **/ + public static class Test { + public static void main(String args[]) { + Averager a = new Averager(); + for(int i = 1; i <= 100; i++) a.addDatum(i); + System.out.println("Average: " + a.getAverage()); + System.out.println("Standard Deviation: " + + a.getStandardDeviation()); + System.out.println("N: " + a.getNum()); + System.out.println("Sum: " + a.getSum()); + System.out.println("Sum of squares: " + a.getSumOfSquares()); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/classes/ColoredRect.java b/src/main/java/com/davidflanagan/examples/classes/ColoredRect.java new file mode 100644 index 0000000..0f04eb8 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/classes/ColoredRect.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.classes; +import java.awt.*; + +/** + * This class subclasses DrawableRect and adds colors to the rectangle it draws + **/ +public class ColoredRect extends DrawableRect { + // These are new fields defined by this class. + // x1, y1, x2, and y2 are inherited from our super-superclass, Rect. + protected Color border, fill; + + /** + * This constructor uses super() to invoke the superclass constructor, and + * also does some initialization of its own. + **/ + public ColoredRect(int x1, int y1, int x2, int y2, + Color border, Color fill) + { + super(x1, y1, x2, y2); + this.border = border; + this.fill = fill; + } + + /** + * This method overrides the draw() method of our superclass so that it + * can make use of the colors that have been specified. + **/ + public void draw(Graphics g) { + g.setColor(fill); + g.fillRect(x1, y1, (x2-x1), (y2-y1)); + g.setColor(border); + g.drawRect(x1, y1, (x2-x1), (y2-y1)); + } +} diff --git a/src/main/java/com/davidflanagan/examples/classes/ComplexNumber.java b/src/main/java/com/davidflanagan/examples/classes/ComplexNumber.java new file mode 100644 index 0000000..00a6c6c --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/classes/ComplexNumber.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.classes; + +/** + * This class represents complex numbers, and defines methods for performing + * arithmetic on complex numbers. + **/ +public class ComplexNumber { + // These are the instance variables. Each ComplexNumber object holds + // two double values, known as x and y. They are private, so they are + // not accessible from outside this class. Instead, they are available + // through the real() and imaginary() methods below. + private double x, y; + + /** This is the constructor. It initializes the x and y variables */ + public ComplexNumber(double real, double imaginary) { + this.x = real; + this.y = imaginary; + } + + /** + * An accessor method. Returns the real part of the complex number. + * Note that there is no setReal() method to set the real part. This means + * that the ComplexNumber class is "immutable". + **/ + public double real() { return x; } + + /** An accessor method. Returns the imaginary part of the complex number */ + public double imaginary() { return y; } + + /** Compute the magnitude of a complex number */ + public double magnitude() { return Math.sqrt(x*x + y*y); } + + /** + * This method converts a ComplexNumber to a string. This is a method of + * Object that we override so that complex numbers can be meaningfully + * converted to strings, and so they can conveniently be printed out with + * System.out.println() and related methods + **/ + public String toString() { return "{" + x + "," + y + "}"; } + + /** + * This is a static class method. It takes two complex numbers, adds + * them, and returns the result as a third number. Because it is static, + * there is no "current instance" or "this" object. Use it like this: + * ComplexNumber c = ComplexNumber.add(a, b); + **/ + public static ComplexNumber add(ComplexNumber a, ComplexNumber b) { + return new ComplexNumber(a.x + b.x, a.y + b.y); + } + + /** + * This is a non-static instance method by the same name. It adds the + * specified complex number to the current complex number. Use it like + * this: + * ComplexNumber c = a.add(b); + **/ + public ComplexNumber add(ComplexNumber a) { + return new ComplexNumber(this.x + a.x, this.y+a.y); + } + + /** A static class method to multiply complex numbers */ + public static ComplexNumber multiply(ComplexNumber a, ComplexNumber b) { + return new ComplexNumber(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x); + } + + /** An instance method to multiply complex numbers */ + public ComplexNumber multiply(ComplexNumber a) { + return new ComplexNumber(x*a.x - y*a.y, x*a.y + y*a.x); + } +} diff --git a/src/main/java/com/davidflanagan/examples/classes/DrawableRect.java b/src/main/java/com/davidflanagan/examples/classes/DrawableRect.java new file mode 100644 index 0000000..c67384b --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/classes/DrawableRect.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.classes; +/** + * This is a subclass of Rect that allows itself to be drawn on a screen. + * It inherits all the fields and methods of Rect + * It relies on the java.awt.Graphics object to perform the drawing. + **/ +public class DrawableRect extends Rect { + /** The DrawableRect constructor just invokes the Rect() constructor */ + public DrawableRect(int x1, int y1, int x2, int y2) { super(x1,y1,x2,y2); } + + /** This is the new method defined by DrawableRect */ + public void draw(java.awt.Graphics g) { + g.drawRect(x1, y1, (x2 - x1), (y2-y1)); + } +} diff --git a/src/main/java/com/davidflanagan/examples/classes/LinkedList.java b/src/main/java/com/davidflanagan/examples/classes/LinkedList.java new file mode 100644 index 0000000..9036912 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/classes/LinkedList.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.classes; + +/** + * This class implements a linked list that can contain any type of object + * that implements the nested Linkable interface. Note that the methods are + * all synchronized, so that it can safely be used by multiple threads at + * the same time. + **/ +public class LinkedList { + /** + * This interface defines the methods required by any object that can be + * linked into a linked list. + **/ + public interface Linkable { + public Linkable getNext(); // Returns the next element in the list + public void setNext(Linkable node); // Sets the next element in the list + } + + // This class has a default constructor: public LinkedList() {} + + /** This is the only field of the class. It holds the head of the list */ + Linkable head; + + /** Return the first node in the list */ + public synchronized Linkable getHead() { return head; } + + /** Insert a node at the beginning of the list */ + public synchronized void insertAtHead(Linkable node) { + node.setNext(head); + head = node; + } + + /** Insert a node at the end of the list */ + public synchronized void insertAtTail(Linkable node) { + if (head == null) head = node; + else { + Linkable p, q; + for(p = head; (q = p.getNext()) != null; p = q) /* no body */; + p.setNext(node); + } + } + + /** Remove and return the node at the head of the list */ + public synchronized Linkable removeFromHead() { + Linkable node = head; + if (node != null) { + head = node.getNext(); + node.setNext(null); + } + return node; + } + + /** Remove and return the node at the end of the list */ + public synchronized Linkable removeFromTail() { + if (head == null) return null; + Linkable p = head, q = null, next = head.getNext(); + if (next == null) { + head = null; + return p; + } + while((next = p.getNext()) != null) { + q = p; + p = next; + } + q.setNext(null); + return p; + } + + /** + * Remove a node matching the specified node from the list. + * Use equals() instead of == to test for a matched node. + **/ + public synchronized void remove(Linkable node) { + if (head == null) return; + if (node.equals(head)) { + head = head.getNext(); + return; + } + Linkable p = head, q = null; + while((q = p.getNext()) != null) { + if (node.equals(q)) { + p.setNext(q.getNext()); + return; + } + p = q; + } + } + + /** This nested class defines a main() method that tests LinkedList */ + public static class Test { + /** + * This is a test class that implements the Linkable interface + **/ + static class LinkableInteger implements Linkable { + int i; // The data contained in the node + Linkable next; // A reference to the next node in the list + public LinkableInteger(int i) { this.i = i; } // Constructor + public Linkable getNext() { return next; } // Part of Linkable + public void setNext(Linkable node) { next = node; } // Linkable + public String toString() { return i + ""; } // For easy printing + public boolean equals(Object o) { // For comparison + if (this == o) return true; + if (!(o instanceof LinkableInteger)) return false; + if (((LinkableInteger)o).i == this.i) return true; + return false; + } + } + + /** + * The test program. Insert some nodes, remove some nodes, then + * print out all elements in the list. It should print out the + * numbers 4, 6, 3, 1, and 5 + **/ + public static void main(String[] args) { + LinkedList ll = new LinkedList(); // Create a list + ll.insertAtHead(new LinkableInteger(1)); // Insert some stuff + ll.insertAtHead(new LinkableInteger(2)); + ll.insertAtHead(new LinkableInteger(3)); + ll.insertAtHead(new LinkableInteger(4)); + ll.insertAtTail(new LinkableInteger(5)); + ll.insertAtTail(new LinkableInteger(6)); + System.out.println(ll.removeFromHead()); // Remove and print a node + System.out.println(ll.removeFromTail()); // Remove and print again + ll.remove(new LinkableInteger(2)); // Remove another one + + // Now print out the contents of the list. + for(Linkable l = ll.getHead(); l != null; l = l.getNext()) + System.out.println(l); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/classes/Randomizer.java b/src/main/java/com/davidflanagan/examples/classes/Randomizer.java new file mode 100644 index 0000000..288250b --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/classes/Randomizer.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.classes; +/** + * This class defines methods for computing pseudo-random numbers, and defines + * the state variable that needs to be maintained for use by those methods. + **/ +public class Randomizer { + // Carefully chosen constants from the book "Numerical Recipes in C". + // All "static final" fields are constants. + static final int m = 233280; + static final int a = 9301; + static final int c = 49297; + + // The state variable maintained by each Randomizer instance + int seed = 1; + + /** + * The constructor for the Randomizer() class. It must be passed some + * arbitrary initial value or "seed" for its pseudo-randomness. + **/ + public Randomizer(int seed) { this.seed = seed; } + + /** + * This method computes a pseudo-random number between 0 and 1 using a very + * simple algorithm. Math.random() and java.util.Random are actually a lot + * better at computing randomness. + **/ + public float randomFloat() { + seed = (seed * a + c) % m; + return (float) Math.abs((float)seed/(float)m); + } + + /** + * This method computes a pseudo-random integer between 0 and specified + * maximum. It uses randomFloat() above. + **/ + public int randomInt(int max) { + return Math.round(max * randomFloat()); + } + + /** + * This nested class is a simple test program: it prints 10 random ints. + * Note how the Randomizer object is seeded using the current time. + **/ + public static class Test { + public static void main(String[] args) { + Randomizer r = new Randomizer((int)new java.util.Date().getTime()); + for(int i = 0; i < 10; i++) System.out.println(r.randomInt(100)); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/classes/Rect.java b/src/main/java/com/davidflanagan/examples/classes/Rect.java new file mode 100644 index 0000000..7101faa --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/classes/Rect.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.classes; +/** + * This class represents a rectangle. Its fields represent the coordinates + * of the corners of the rectangle. Its methods define operations that can + * be performed on Rect objects. + **/ +public class Rect { + // These are the data fields of the class + public int x1, y1, x2, y2; + + /** + * The is the main constructor for the class. It simply uses its arguments + * to initialize each of the fields of the new object. Note that it has + * the same name as the class, and that it has no return value declared in + * its signature. + **/ + public Rect(int x1, int y1, int x2, int y2) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + /** + * This is another constructor. It defines itself in terms of the above + **/ + public Rect(int width, int height) { this(0, 0, width, height); } + + /** This is yet another constructor. */ + public Rect() { this(0, 0, 0, 0); } + + /** Move the rectangle by the specified amounts */ + public void move(int deltax, int deltay) { + x1 += deltax; x2 += deltax; + y1 += deltay; y2 += deltay; + } + + /** Test whether the specified point is inside the rectangle */ + public boolean isInside(int x, int y) { + return ((x >= x1)&& (x <= x2)&& (y >= y1)&& (y <= y2)); + } + + /** + * Return the union of this rectangle with another. I.e. return the + * smallest rectangle that includes them both. + **/ + public Rect union(Rect r) { + return new Rect((this.x1 < r.x1) ? this.x1 : r.x1, + (this.y1 < r.y1) ? this.y1 : r.y1, + (this.x2 > r.x2) ? this.x2 : r.x2, + (this.y2 > r.y2) ? this.y2 : r.y2); + } + + /** + * Return the intersection of this rectangle with another. + * I.e. return their overlap. + **/ + public Rect intersection(Rect r) { + Rect result = new Rect((this.x1 > r.x1) ? this.x1 : r.x1, + (this.y1 > r.y1) ? this.y1 : r.y1, + (this.x2 < r.x2) ? this.x2 : r.x2, + (this.y2 < r.y2) ? this.y2 : r.y2); + if (result.x1 > result.x2) { result.x1 = result.x2 = 0; } + if (result.y1 > result.y2) { result.y1 = result.y2 = 0; } + return result; + } + + /** + * This is a method of our superclass, Object. We override it so that + * Rect objects can be meaningfully converted to strings, can be + * concatenated to strings with the + operator, and can be passed to + * methods like System.out.println() + **/ + public String toString() { + return "[" + x1 + "," + y1 + "; " + x2 + "," + y2 + "]"; + } +} diff --git a/src/main/java/com/davidflanagan/examples/classes/RectTest.java b/src/main/java/com/davidflanagan/examples/classes/RectTest.java new file mode 100644 index 0000000..d5bc8e1 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/classes/RectTest.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.classes; + +/** This class demonstrates how you might use the Rect class */ +public class RectTest { + public static void main(String[] args) { + Rect r1 = new Rect(1, 1, 4, 4); // Create Rect objects + Rect r2 = new Rect(2, 3, 5, 6); + Rect u = r1.union(r2); // Invoke Rect methods + Rect i = r2.intersection(r1); + + if (u.isInside(r2.x1, r2.y1)) // Use Rect fields and invoke a method + System.out.println("(" + r2.x1 + "," + r2.y1 + + ") is inside the union"); + + // These lines implicitly call the Rect.toString() method + System.out.println(r1 + " union " + r2 + " = " + u); + System.out.println(r1 + " intersect " + r2 + " = " + i); + } +} diff --git a/src/main/java/com/davidflanagan/examples/classes/Sorter.java b/src/main/java/com/davidflanagan/examples/classes/Sorter.java new file mode 100644 index 0000000..afaf2a1 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/classes/Sorter.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.classes; +// These are some classes we need for internationalized string sorting +import java.text.Collator; +import java.text.CollationKey; +import java.util.Locale; + +/** + * This class defines a bunch of static methods for efficiently sorting + * arrays of Strings or other objects. It also defines two interfaces that + * provide two different ways of comparing objects to be sorted. + **/ +public class Sorter { + /** + * This interface defines the compare() method used to compare two objects. + * To sort objects of a given type, you must provide a Comparer + * object with a compare() method that orders those objects as desired + **/ + public static interface Comparer { + /** + * Compare objects, return a value that indicates their relative order: + * if (a > b) return > 0; + * if (a == b) return 0; + * if (a < b) return < 0. + **/ + public int compare(Object a, Object b); + } + + /** + * This is an alternative interface that can be used to order objects. If + * a class implements this Comparable interface, then any two instances of + * that class can be directly compared by invoking the compareTo() method. + **/ + public static interface Comparable { + /** + * Compare objects, return a value that indicates their relative order: + * if (this > other) return > 0 + * if (this == other) return 0 + * if (this < other) return < 0 + **/ + public int compareTo(Object other); + } + + /** + * This is an internal Comparer object (created with an anonymous class) + * that compares two ASCII strings. + * It is used in the sortAscii methods below. + **/ + private static Comparer ascii_comparer = new Comparer() { + public int compare(Object a, Object b) { + return ((String)a).compareTo((String)b); + } + }; + + /** + * This is another internal Comparer object. It is used to compare two + * Comparable objects. It is used by the sort() methods below that take + * Comparable objects as arguments instead of arbitrary objects + **/ + private static Comparer comparable_comparer = new Comparer() { + public int compare(Object a, Object b) { + return ((Comparable)a).compareTo(b); + } + }; + + /** Sort an array of ASCII strings into ascending order */ + public static void sortAscii(String[] a) { + // Note use of the ascii_comparer object + sort(a, null, 0, a.length-1, true, ascii_comparer); + } + + /** + * Sort a portion of an array of ASCII strings into ascending or descending + * order, depending on the argument up + **/ + public static void sortAscii(String[] a, int from, int to, boolean up) { + // Note use of the ascii_comparer object + sort(a, null, from, to, up, ascii_comparer); + } + + /** Sort an array of ASCII strings into ascending order, ignoring case */ + public static void sortAsciiIgnoreCase(String[] a) { + sortAsciiIgnoreCase(a, 0, a.length-1, true); + } + + /** + * Sort an portion of an array of ASCII strings, ignoring case. Sort into + * ascending order if up is true, otherwise sort into descending order. + **/ + public static void sortAsciiIgnoreCase(String[] a, int from, int to, + boolean up) { + if ((a == null) || (a.length < 2)) return; + // Create a secondary array of strings that contains lowercase versions + // of all the specified strings. + String b[] = new String[a.length]; + for(int i = 0; i < a.length; i++) b[i] = a[i].toLowerCase(); + // Sort that secondary array, and rearrange the original array + // in exactly the same way, resulting in a case-insensitive sort. + // Note the use of the ascii_comparer object + sort(b, a, from, to, up, ascii_comparer); + } + + /** + * Sort an array of strings into ascending order, using the correct + * collation order for the default locale + **/ + public static void sort(String[] a) { + sort(a, 0, a.length-1, true, false, null); + } + + /** + * Sort a portion of an array of strings, using the collation order of + * the default locale. If up is true, sort ascending, otherwise, sort + * descending. If ignorecase is true, ignore the capitalization of letters + **/ + public static void sort(String[] a, int from, int to, + boolean up, boolean ignorecase) { + sort(a, from, to, up, ignorecase, null); + } + + /** + * Sort a portion of an array of strings, using the collation order of + * the specified locale. If up is true, sort ascending, otherwise, sort + * descending. If ignorecase is true, ignore the capitalization of letters + **/ + public static void sort(String[] a, int from, int to, + boolean up, boolean ignorecase, + Locale locale) { + // Don't sort if we don't have to + if ((a == null) || (a.length < 2)) return; + + // The java.text.Collator object does internationalized string compares + // Create one for the specified, or the default locale. + Collator c; + if (locale == null) c = Collator.getInstance(); + else c = Collator.getInstance(locale); + + // Specify whether or not case should be considered in the sort. + // Note: this option does not seem to work correctly in JDK 1.1.1 + // using the default American English locale. + if (ignorecase) c.setStrength(Collator.SECONDARY); + + // Use the Collator object to create an array of CollationKey objects + // that correspond to each of the strings. + // Comparing CollationKeys is much quicker than comparing Strings + CollationKey[] b = new CollationKey[a.length]; + for(int i = 0; i < a.length; i++) b[i] = c.getCollationKey(a[i]); + + // Now define a Comparer object to compare collation keys, using an + // anonymous class. + Comparer comp = new Comparer() { + public int compare(Object a, Object b) { + return ((CollationKey)a).compareTo((CollationKey)b); + } + }; + + // Finally, sort the array of CollationKey objects, rearranging the + // original array of strings in exactly the same way. + sort(b, a, from, to, up, comp); + } + + /** Sort an array of Comparable objects into ascending order */ + public static void sort(Comparable[] a) { + sort(a, null, 0, a.length-1, true); + } + + /** + * Sort a portion of an array of Comparable objects. If up is true, + * sort into ascending order, otherwise sort into descending order. + **/ + public static void sort(Comparable[] a, int from, int to, boolean up) { + sort(a, null, from, to, up, comparable_comparer); + } + + /** + * Sort a portion of array a of Comparable objects. If up is true, + * sort into ascending order, otherwise sort into descending order. + * Re-arrange the array b in exactly the same way as a. + **/ + public static void sort(Comparable[] a, Object[] b, + int from, int to, boolean up) { + sort(a, b, from, to, up, comparable_comparer); + } + + /** + * Sort an array of arbitrary objects into ascending order, using the + * comparison defined by the Comparer object c + **/ + public static void sort(Object[] a, Comparer c) { + sort(a, null, 0, a.length-1, true, c); + } + + /** + * Sort a portion of an array of objects, using the comparison defined by + * the Comparer object c. If up is true, sort into ascending order, + * otherwise sort into descending order. + **/ + public static void sort(Object[] a, int from, int to, boolean up, + Comparer c) + { + sort(a, null, from, to, up, c); + } + + /** + * This is the main sort() routine. It performs a quicksort on the elements + * of array a between the element from and the element to. The up argument + * specifies whether the elements should be sorted into ascending (true) or + * descending (false) order. The Comparer argument c is used to perform + * comparisons between elements of the array. The elements of the array b + * are reordered in exactly the same way as the elements of array a are. + **/ + public static void sort(Object[] a, Object[] b, + int from, int to, + boolean up, Comparer c) + { + // If there is nothing to sort, return + if ((a == null) || (a.length < 2)) return; + + // This is the basic quicksort algorithm, stripped of frills that can + // make it faster but even more confusing than it already is. You + // should understand what the code does, but don't have to understand + // just why it is guaranteed to sort the array... + // Note the use of the compare() method of the Comparer object. + int i = from, j = to; + Object center = a[(from + to) / 2]; + do { + if (up) { // an ascending sort + while((i < to)&& (c.compare(center, a[i]) > 0)) i++; + while((j > from)&& (c.compare(center, a[j]) < 0)) j--; + } else { // a descending sort + while((i < to)&& (c.compare(center, a[i]) < 0)) i++; + while((j > from)&& (c.compare(center, a[j]) > 0)) j--; + } + if (i < j) { + Object tmp = a[i]; a[i] = a[j]; a[j] = tmp; // swap elements + if (b != null) { tmp = b[i]; b[i] = b[j]; b[j] = tmp; } // swap + } + if (i <= j) { i++; j--; } + } while(i <= j); + if (from < j) sort(a, b, from, j, up, c); // recursively sort the rest + if (i < to) sort(a, b, i, to, up, c); + } + + /** + * This nested class defines a test program that demonstrates several + * ways to use the Sorter class to sort ComplexNumber objects + **/ + public static class Test { + /** + * This subclass of ComplexNumber implements the Comparable interface + * and defines a compareTo() method for comparing complex numbers. + * It compares numbers based on their magnitude. I.e. on their distance + * from the origin. + **/ + static class SortableComplexNumber extends ComplexNumber + implements Comparable { + public SortableComplexNumber(double x, double y) { super(x, y); } + public int compareTo(Object other) { + return sign(this.magnitude()-((ComplexNumber)other).magnitude()); + } + } + + /** A a test program that sorts complex numbers in various ways. */ + public static void main(String[] args) { + // Define an array of SortableComplexNumber objects. Initialize it + // to contain random complex numbers. + SortableComplexNumber[] a = new SortableComplexNumber[5]; + for(int i = 0; i < a.length; i++) + a[i] = new SortableComplexNumber(Math.random()*10, + Math.random()*10); + + // Now sort it using the SortableComplexNumber compareTo() method, + // which sorts by magnitude, and print the results out. + System.out.println("Sorted by magnitude:"); + Sorter.sort(a); + for(int i = 0; i < a.length; i++) System.out.println(a[i]); + + // Sort the complex numbers again, using a Comparer object that + // compares them based on the sum of their real and imaginary parts + System.out.println("Sorted by sum of real and imaginary parts:"); + Sorter.sort(a, new Comparer() { + public int compare(Object a, Object b) { + ComplexNumber i = (ComplexNumber)a; + ComplexNumber j = (ComplexNumber)b; + return sign((i.real() + i.imaginary()) - + (j.real() + j.imaginary())); + } + }); + for(int i = 0; i < a.length; i++) System.out.println(a[i]); + + // Sort them again using a Comparer object that compares their real + // parts, and then their imaginary parts + System.out.println("Sorted descending by real, then imaginary:"); + Sorter.sort(a, 0, a.length-1, false, new Comparer() { + public int compare(Object a, Object b) { + ComplexNumber i = (ComplexNumber) a; + ComplexNumber j = (ComplexNumber) b; + double result = i.real() - j.real(); + if (result == 0) result = i.imaginary()-j.imaginary(); + return sign(result); + } + }); + for(int i = 0; i < a.length; i++) System.out.println(a[i]); + } + + /** This is a convenience routine used by comparison routines */ + public static int sign(double x) { + if (x > 0) return 1; + else if (x < 0) return -1; + else return 0; + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/datatransfer/Scribble.java b/src/main/java/com/davidflanagan/examples/datatransfer/Scribble.java new file mode 100644 index 0000000..d893a0b --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/datatransfer/Scribble.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.datatransfer; +import java.awt.*; +import java.awt.geom.*; +import java.awt.datatransfer.*; +import java.io.Serializable; +import java.util.StringTokenizer; + +/** + * This class represents a scribble composed of any number of "polylines". + * Each "polyline" is set of connected line segments. A scribble is created + * through a series of calls to the moveto() and lineto() methods. moveto() + * specifies the starting point of a new polyline, and lineto() adds a new + * point to the end of the current polyline(). + * + * This class implements the Shape interface which means that it can be drawn + * using the Java2D graphics API + * + * It also implements the Transferable interface, which means that it can + * easily be used with cut-and-paste and drag-and-drop. It defines a custom + * DataFlavor, scribbleDataFlavor, which transfers Scribble objects as Java + * objects. However, it also supports cut-and-paste and drag-and-drop based + * on a portable string representation of the scribble. The toString() + * and parse() methods write and read this string format + **/ +public class Scribble implements Shape, Transferable, Serializable, Cloneable { + protected double[] points = new double[64]; // The scribble data + protected int numPoints = 0; // The current number of points + double maxX = Double.NEGATIVE_INFINITY; // The bounding box + double maxY = Double.NEGATIVE_INFINITY; + double minX = Double.POSITIVE_INFINITY; + double minY = Double.POSITIVE_INFINITY; + + /** + * Begin a new polyline at (x,y). Note the use of Double.NaN in the + * points array to mark the beginning of a new polyline + **/ + public void moveto(double x, double y) { + if (numPoints + 3 > points.length) reallocate(); + // Mark this as the beginning of a new line + points[numPoints++] = Double.NaN; + // The rest of this method is just like lineto(); + lineto(x, y); + } + + /** + * Add the point (x,y) to the end of the current polyline + **/ + public void lineto(double x, double y) { + if (numPoints + 2 > points.length) reallocate(); + points[numPoints++] = x; + points[numPoints++] = y; + + // See if the point enlarges our bounding box + if (x > maxX) maxX = x; + if (x < minX) minX = x; + if (y > maxY) maxY = y; + if (y < minY) minY = y; + } + + /** + * Append the Scribble s to this Scribble + **/ + public void append(Scribble s) { + int n = numPoints + s.numPoints; + double[] newpoints = new double[n]; + System.arraycopy(points, 0, newpoints, 0, numPoints); + System.arraycopy(s.points, 0, newpoints, numPoints, s.numPoints); + points = newpoints; + numPoints = n; + minX = Math.min(minX, s.minX); + maxX = Math.max(maxX, s.maxX); + minY = Math.min(minY, s.minY); + maxY = Math.max(maxY, s.maxY); + } + + /** + * Translate the coordinates of all points in the Scribble by x,y + **/ + public void translate(double x, double y) { + for(int i = 0; i < numPoints; i++) { + if (Double.isNaN(points[i])) continue; + points[i++] += x; + points[i] += y; + } + minX += x; maxX += x; + minY += y; maxY += y; + } + + /** An internal method to make more room in the data array */ + protected void reallocate() { + double[] newpoints = new double[points.length * 2]; + System.arraycopy(points, 0, newpoints, 0, numPoints); + points = newpoints; + } + + /** Clone a Scribble object and its internal array of data */ + public Object clone() { + try { + Scribble s = (Scribble) super.clone(); // make a copy of all fields + s.points = (double[]) points.clone(); // copy the entire array + return s; + } + catch (CloneNotSupportedException e) { // This should never happen + return this; + } + } + + /** Convert the scribble data to a textual format */ + public String toString() { + StringBuffer b = new StringBuffer(); + for(int i = 0; i < numPoints; i++) { + if (Double.isNaN(points[i])) { + b.append("m "); + } + else { + b.append(points[i]); + b.append(' '); + } + } + return b.toString(); + } + + /** + * Create a new Scribble object and initialize it by parsing a string of + * coordinate data in the format produced by toString() + **/ + public static Scribble parse(String s) throws NumberFormatException { + StringTokenizer st = new StringTokenizer(s); + Scribble scribble = new Scribble(); + while(st.hasMoreTokens()) { + String t = st.nextToken(); + if (t.charAt(0) == 'm') { + scribble.moveto(Double.parseDouble(st.nextToken()), + Double.parseDouble(st.nextToken())); + } + else { + scribble.lineto(Double.parseDouble(t), + Double.parseDouble(st.nextToken())); + } + } + return scribble; + } + + // ========= The following methods implement the Shape interface ======== + + /** Return the bounding box of the Shape */ + public Rectangle getBounds() { + return new Rectangle((int)(minX-0.5f), (int)(minY-0.5f), + (int)(maxX-minX+0.5f), (int)(maxY-minY+0.5f)); + } + + /** Return the bounding box of the Shape */ + public Rectangle2D getBounds2D() { + return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY); + } + + /** Our shape is an open curve, so it never contains anything */ + public boolean contains(Point2D p) { return false; } + public boolean contains(Rectangle2D r) { return false; } + public boolean contains(double x, double y) { return false; } + public boolean contains(double x, double y, double w, double h) { + return false; + } + + /** + * Determine if the scribble intersects the specified rectangle by testing + * each line segment individually + **/ + public boolean intersects(Rectangle2D r) { + if (numPoints < 4) return false; + int i = 0; + double x1, y1, x2 = 0.0, y2 = 0.0; + while(i < numPoints) { + if (Double.isNaN(points[i])) { // If we're beginning a new line + i++; // Skip the NaN + x2 = points[i++]; + y2 = points[i++]; + } + else { + x1 = x2; + y1 = y2; + x2 = points[i++]; + y2 = points[i++]; + if (r.intersectsLine(x1, y1, x2, y2)) return true; + } + } + + return false; + } + + /** Test for intersection by invoking the method above */ + public boolean intersects(double x, double y, double w, double h){ + return intersects(new Rectangle2D.Double(x,y,w,h)); + } + + /** + * Return a PathIterator object that tells Java2D how to draw this scribble + **/ + public PathIterator getPathIterator(AffineTransform at) { + return new ScribbleIterator(at); + } + + /** + * Return a PathIterator that doesn't include curves. Ours never does. + **/ + public PathIterator getPathIterator(AffineTransform at, double flatness) { + return getPathIterator(at); + } + + /** + * This inner class implements the PathIterator interface to describe + * the shape of a scribble. Since a Scribble is composed of arbitrary + * movetos and linetos, we simply return their coordinates + **/ + public class ScribbleIterator implements PathIterator { + protected int i = 0; // Position in array + protected AffineTransform transform; + + public ScribbleIterator(AffineTransform transform) { + this.transform = transform; + } + + /** How to determine insideness and outsideness for this shape */ + public int getWindingRule() { return PathIterator.WIND_NON_ZERO; } + + /** Have we reached the end of the scribble path yet? */ + public boolean isDone() { return i >= numPoints; } + + /** Move on to the next segment of the path */ + public void next() { + if (Double.isNaN(points[i])) i += 3; + else i += 2; + } + + /** + * Get the coordinates of the current moveto or lineto as floats + **/ + public int currentSegment(float[] coords) { + int retval; + if (Double.isNaN(points[i])) { // If its a moveto + coords[0] = (float)points[i+1]; + coords[1] = (float)points[i+2]; + retval = SEG_MOVETO; + } + else { + coords[0] = (float)points[i]; + coords[1] = (float)points[i+1]; + retval = SEG_LINETO; + } + + // If a transform was specified, use it on the coordinates + if (transform != null) transform.transform(coords, 0, coords, 0,1); + + return retval; + } + + /** + * Get the coordinates of the current moveto or lineto as doubles + **/ + public int currentSegment(double[] coords) { + int retval; + if (Double.isNaN(points[i])) { + coords[0] = points[i+1]; + coords[1] = points[i+2]; + retval = SEG_MOVETO; + } + else { + coords[0] = points[i]; + coords[1] = points[i+1]; + retval = SEG_LINETO; + } + if (transform != null) transform.transform(coords, 0, coords, 0,1); + return retval; + } + } + + //====== The following methods implement the Transferable interface ===== + + // This is the custom DataFlavor for Scribble objects + public static DataFlavor scribbleDataFlavor = + new DataFlavor(Scribble.class, "Scribble"); + + // This is a list of the flavors we know how to work with + public static DataFlavor[] supportedFlavors = { + scribbleDataFlavor, + DataFlavor.stringFlavor + }; + + /** Return the data formats or "flavors" we know how to transfer */ + public DataFlavor[] getTransferDataFlavors() { + return (DataFlavor[]) supportedFlavors.clone(); + } + + /** Check whether we support a given flavor */ + public boolean isDataFlavorSupported(DataFlavor flavor) { + return (flavor.equals(scribbleDataFlavor) || + flavor.equals(DataFlavor.stringFlavor)); + } + + /** + * Return the scribble data in the requested format, or throw an exception + * if we don't support the requested format + **/ + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException + { + if (flavor.equals(scribbleDataFlavor)) { return this; } + else if (flavor.equals(DataFlavor.stringFlavor)) { return toString(); } + else throw new UnsupportedFlavorException(flavor); + } +} diff --git a/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleCutAndPaste.java b/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleCutAndPaste.java new file mode 100644 index 0000000..5fde190 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleCutAndPaste.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.datatransfer; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.awt.datatransfer.*; // Clipboard, Transferable, DataFlavor, etc. + +/** + * This component allows the user to scribble in a window, and to cut and + * paste scribbles between windows. It stores mouse coordinates in a Scribble + * object, which is used to draw the scribble, and also to transfer the + * scribble to and from the clipboard. A JPopupMenu provides access to the + * cut, copy, and paste commands. + **/ +public class ScribbleCutAndPaste extends JComponent + implements ActionListener, ClipboardOwner +{ + Stroke linestyle = new BasicStroke(3.0f); // Draw with wide lines + Scribble scribble = new Scribble(); // Holds our scribble + Scribble selection; // A copy of the scribble as cut + JPopupMenu popup; // A menu for cut-and-paste + + public ScribbleCutAndPaste() { + // Create the popup menu. + String[] labels = new String[] { "Clear", "Cut", "Copy", "Paste" }; + String[] commands = new String[] { "clear", "cut", "copy", "paste" }; + popup = new JPopupMenu(); // Create the menu + popup.setLabel("Edit"); + for(int i = 0; i < labels.length; i++) { + JMenuItem mi = new JMenuItem(labels[i]); // Create a menu item + mi.setActionCommand(commands[i]); // Set its action command + mi.addActionListener(this); // And its action listener + popup.add(mi); // Add item to the menu + } + // Finally, register the popup menu with the component it appears over + this.add(popup); + + // Add event listeners to do the drawing and handle the popup + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) + popup.show((Component)e.getSource(), + e.getX(), e.getY()); + else + scribble.moveto(e.getX(), e.getY()); // start new line + } + }); + + addMouseMotionListener(new MouseMotionAdapter() { + public void mouseDragged(MouseEvent e) { + // If this isn't mouse button 1, ignore it + if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == 0) + return; + scribble.lineto(e.getX(), e.getY()); // Add a line + repaint(); + } + }); + } + + /** + * Draw the component. + * This method relies on Scribble which implements Shape. + **/ + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + g2.setStroke(linestyle); // Specify wide lines + g2.draw(scribble); // Draw the scribble + } + + /** This is the ActionListener method invoked by the popup menu items */ + public void actionPerformed(ActionEvent event) { + String command = event.getActionCommand(); + if (command.equals("clear")) clear(); + else if (command.equals("cut")) cut(); + else if (command.equals("copy")) copy(); + else if (command.equals("paste")) paste(); + } + + /** Clear the scribble. Invoked by popup menu */ + void clear() { + scribble = new Scribble(); // Get a new, empty scribble + repaint(); // And redraw everything. + } + + /** + * Make a copy of the current Scribble and put it on the clipboard + * We can do this because Scribble implements Transferable + * The user invokes this method through the popup menu + **/ + public void copy() { + // Get system clipboard + Clipboard c = this.getToolkit().getSystemClipboard(); + + // Make a copy of the Scribble object to put on the clipboard + selection = (Scribble) scribble.clone(); + + // Put the copy on the clipboard + c.setContents(selection, // What to put on the clipboard + this); // Who to notify when it is no longer there + } + + /** + * The cut action is just like the copy action, except that we erase the + * current scribble after copying it to the clipboard + **/ + public void cut() { + copy(); + clear(); + } + + /** + * The user invokes this method through the popup menu. + * First, ask for the Transferable contents of the system clipboard. + * Then ask that Transferable object for the scribble data it represents. + * Try using both data flavors supported by the Scribble class. + * If it doesn't work, beep to tell the user it failed. + **/ + public void paste() { + Clipboard c = this.getToolkit().getSystemClipboard(); // Get clipboard + Transferable t = c.getContents(this); // Get its contents + + // Now try to get a Scribble object from the transferrable + Scribble pastedScribble = null; + try { + pastedScribble = + (Scribble)t.getTransferData(Scribble.scribbleDataFlavor); + } + catch (Exception e) { // UnsupportedFlavor, NullPointer, etc. + // If that didn't work, try asking for a string instead. + try { + String s = (String)t.getTransferData(DataFlavor.stringFlavor); + // We got a string, so try converting it to a Scribble + pastedScribble = Scribble.parse(s); + } + catch (Exception e2) { // UnsupportedFlavor, NumberFormat, etc. + // If we couldn't get and parse a string, give up + this.getToolkit().beep(); // Tell the user the paste failed + return; + } + } + + // If we get here, we've retrieved a Scribble object from the clipboard + // Add it to the current scribble, and ask to be redrawn + scribble.append(pastedScribble); + repaint(); + } + + /** + * This method implements the ClipboardOwner interface. We specify a + * ClipboardOwner when we copy a Scribble to the clipboard. This method + * will be invoked when something else is copied to the clipboard, and + * bumps our data off the clipboard. When this method is invoked we no + * longer have to maintain our copied Scribble object, since it is no + * longer available to be pasted. Often, a component will highlight a + * selected object while it is on the clipboard, and will use this method + * to un-highlight the object when it is no longer on the clipboard. + **/ + public void lostOwnership(Clipboard c, Transferable t) { + selection = null; + } + + /** A simple main method to test the class. */ + public static void main(String[] args) { + JFrame frame = new JFrame("ScribbleCutAndPaste"); + ScribbleCutAndPaste s = new ScribbleCutAndPaste(); + frame.getContentPane().add(s, BorderLayout.CENTER); + frame.setSize(400, 400); + frame.setVisible(true); + } +} diff --git a/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleDragAndDrop.java b/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleDragAndDrop.java new file mode 100644 index 0000000..4f5302f --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/datatransfer/ScribbleDragAndDrop.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.datatransfer; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.border.*; +import java.awt.datatransfer.*; // Clipboard, Transferable, DataFlavor, etc. +import java.awt.dnd.*; +import java.util.ArrayList; + +/** + * This component can operate in two modes. In "draw mode", it allows the user + * to scribble with the mouse. In "drag mode", it allows the user to drag + * scribbles with the mouse. Regardless of the mode, it always allows + * scribbles to be dropped on it from other applications. + **/ +public class ScribbleDragAndDrop extends JComponent + implements DragGestureListener, // For recognizing the start of drags + DragSourceListener, // For processing drag source events + DropTargetListener, // For processing drop target events + MouseListener, // For processing mouse clicks + MouseMotionListener // For processing mouse drags +{ + ArrayList scribbles = new ArrayList(); // A list of Scribbles to draw + Scribble currentScribble; // The scribble in progress + Scribble beingDragged; // The scribble being dragged + DragSource dragSource; // A central DnD object + boolean dragMode; // Are we dragging or scribbling? + + // These are some constants we use + static final int LINEWIDTH = 3; + static final BasicStroke linestyle = new BasicStroke(LINEWIDTH); + static final Border normalBorder = new BevelBorder(BevelBorder.LOWERED); + static final Border dropBorder = new BevelBorder(BevelBorder.RAISED); + + /** The constructor: set up drag-and-drop stuff */ + public ScribbleDragAndDrop() { + // Give ourselves a nice default border. + // We'll change this border during drag-and-drop. + setBorder(normalBorder); + + // Register listeners to handle drawing + addMouseListener(this); + addMouseMotionListener(this); + + // Create a DragSource and DragGestureRecognizer to listen for drags + // The DragGestureRecognizer will notify the DragGestureListener + // when the user tries to drag an object + dragSource = DragSource.getDefaultDragSource(); + dragSource.createDefaultDragGestureRecognizer(this, // What component + DnDConstants.ACTION_COPY_OR_MOVE, // What drag types? + this);// the listener + + // Create and set up a DropTarget that will listen for drags and + // drops over this component, and will notify the DropTargetListener + DropTarget dropTarget = new DropTarget(this, // component to monitor + this); // listener to notify + this.setDropTarget(dropTarget); // Tell the component about it. + } + + /** + * The component draws itself by drawing each of the Scribble objects. + **/ + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + g2.setStroke(linestyle); // Specify wide lines + + int numScribbles = scribbles.size(); + for(int i = 0; i < numScribbles; i++) { + Scribble s = (Scribble)scribbles.get(i); + g2.draw(s); // Draw the scribble + } + } + + public void setDragMode(boolean dragMode) { + this.dragMode = dragMode; + } + public boolean getDragMode() { return dragMode; } + + /** + * This method, and the following four methods are from the MouseListener + * interface. If we're in drawing mode, this method handles mouse down + * events and starts a new scribble. + **/ + public void mousePressed(MouseEvent e) { + if (dragMode) return; + currentScribble = new Scribble(); + scribbles.add(currentScribble); + currentScribble.moveto(e.getX(), e.getY()); + } + public void mouseReleased(MouseEvent e) {} + public void mouseClicked(MouseEvent e) {} + public void mouseEntered(MouseEvent e) {} + public void mouseExited(MouseEvent e) {} + + /** + * This method and mouseMoved() below are from the MouseMotionListener + * interface. If we're in drawing mode, this method adds a new point + * to the current scribble and requests a redraw + **/ + public void mouseDragged(MouseEvent e) { + if (dragMode) return; + currentScribble.lineto(e.getX(), e.getY()); + repaint(); + } + public void mouseMoved(MouseEvent e) {} + + /** + * This method implements the DragGestureListener interface. It will be + * invoked when the DragGestureRecognizer thinks that the user has + * initiated a drag. If we're not in drawing mode, then this method will + * try to figure out which Scribble object is being dragged, and will + * initiate a drag on that object. + **/ + public void dragGestureRecognized(DragGestureEvent e) { + // Don't drag if we're not in drag mode + if (!dragMode) return; + + // Figure out where the drag started + MouseEvent inputEvent = (MouseEvent) e.getTriggerEvent(); + int x = inputEvent.getX(); + int y = inputEvent.getY(); + + // Figure out which scribble was clicked on, if any by creating a + // small rectangle around the point and testing for intersection. + Rectangle r = new Rectangle (x-LINEWIDTH, y-LINEWIDTH, + LINEWIDTH*2, LINEWIDTH*2); + int numScribbles = scribbles.size(); + for(int i = 0; i < numScribbles; i++) { // Loop through the scribbles + Scribble s = (Scribble) scribbles.get(i); + if (s.intersects(r)) { + // The user started the drag on top of this scribble, so + // start to drag it. + + // First, remember which scribble is being dragged, so we can + // delete it later (if this is a move rather than a copy) + beingDragged = s; + + // Next, create a copy that will be the one dragged + Scribble dragScribble = (Scribble) s.clone(); + // Adjust the origin to the point the user clicked on. + dragScribble.translate(-x, -y); + + // Choose a cursor based on the type of drag the user initiated + Cursor cursor; + switch(e.getDragAction()) { + case DnDConstants.ACTION_COPY: + cursor = DragSource.DefaultCopyDrop; + break; + case DnDConstants.ACTION_MOVE: + cursor = DragSource.DefaultMoveDrop; + break; + default: + return; // We only support move and copys + } + + // Some systems allow us to drag an image along with the + // cursor. If so, create an image of the scribble to drag + if (dragSource.isDragImageSupported()) { + Rectangle scribbleBox = dragScribble.getBounds(); + Image dragImage = this.createImage(scribbleBox.width, + scribbleBox.height); + Graphics2D g = (Graphics2D)dragImage.getGraphics(); + g.setColor(new Color(0,0,0,0)); // transparent background + g.fillRect(0, 0, scribbleBox.width, scribbleBox.height); + g.setColor(Color.black); + g.setStroke(linestyle); + g.translate(-scribbleBox.x, -scribbleBox.y); + g.draw(dragScribble); + Point hotspot = new Point(-scribbleBox.x, -scribbleBox.y); + + // Now start dragging, using the image. + e.startDrag(cursor, dragImage, hotspot, dragScribble,this); + } + else { + // Or start the drag without an image + e.startDrag(cursor, dragScribble,this); + } + // After we've started dragging one scribble, stop looking + return; + } + } + } + + /** + * This method, and the four unused methods that follow it implement the + * DragSourceListener interface. dragDropEnd() is invoked when the user + * drops the scribble she was dragging. If the drop was successful, and + * if the user did a "move" rather than a "copy", then we delete the + * dragged scribble from the list of scribbles to draw. + **/ + public void dragDropEnd(DragSourceDropEvent e) { + if (!e.getDropSuccess()) return; + int action = e.getDropAction(); + if (action == DnDConstants.ACTION_MOVE) { + scribbles.remove(beingDragged); + beingDragged = null; + repaint(); + } + } + + // These methods are also part of DragSourceListener. + // They are invoked at interesting points during the drag, and can be + // used to perform "drag over" effects, such as changing the drag cursor + // or drag image. + public void dragEnter(DragSourceDragEvent e) {} + public void dragExit(DragSourceEvent e) {} + public void dropActionChanged(DragSourceDragEvent e) {} + public void dragOver(DragSourceDragEvent e) {} + + // The next five methods implement DropTargetListener + + /** + * This method is invoked when the user first drags something over us. + * If we understand the data type being dragged, then call acceptDrag() + * to tell the system that we're receptive. Also, we change our border + * as a "drag under" effect to signal that we can accept the drop. + **/ + public void dragEnter(DropTargetDragEvent e) { + if (e.isDataFlavorSupported(Scribble.scribbleDataFlavor) || + e.isDataFlavorSupported(DataFlavor.stringFlavor)) { + e.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + this.setBorder(dropBorder); + } + } + + /** The user is no longer dragging over us, so restore the border */ + public void dragExit(DropTargetEvent e) { this.setBorder(normalBorder); } + + /** + * This is the key method of DropTargetListener. It is invoked when the + * user drops something on us. + **/ + public void drop(DropTargetDropEvent e) { + this.setBorder(normalBorder); // Restore the default border + + // First, check whether we understand the data that was dropped. + // If we supports our data flavors, accept the drop, otherwise reject. + if (e.isDataFlavorSupported(Scribble.scribbleDataFlavor) || + e.isDataFlavorSupported(DataFlavor.stringFlavor)) { + e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); + } + else { + e.rejectDrop(); + return; + } + + // We've accepted the drop, so now we attempt to get the dropped data + // from the Transferable object. + Transferable t = e.getTransferable(); // Holds the dropped data + Scribble droppedScribble; // This will hold the Scribble object + + // First, try to get the data directly as a scribble object + try { + droppedScribble = + (Scribble) t.getTransferData(Scribble.scribbleDataFlavor); + } + catch (Exception ex) { // unsupported flavor, IO exception, etc. + // If that doesn't work, try to get it as a String and parse it + try { + String s = (String) t.getTransferData(DataFlavor.stringFlavor); + droppedScribble = Scribble.parse(s); + } + catch(Exception ex2) { + // If we still couldn't get the data, tell the system we failed + e.dropComplete(false); + return; + } + } + + // If we get here, we've got the Scribble object + Point p = e.getLocation(); // Where did the drop happen? + droppedScribble.translate(p.getX(), p.getY()); // Move it there + scribbles.add(droppedScribble); // add to display list + repaint(); // ask for redraw + e.dropComplete(true); // signal success! + } + + // These are unused DropTargetListener methods + public void dragOver(DropTargetDragEvent e) {} + public void dropActionChanged(DropTargetDragEvent e) {} + + /** + * The main method. Creates a simple application using this class. Note + * the buttons for switching between draw mode and drag mode. + **/ + public static void main(String[] args) { + // Create a frame and put a scribble pane in it + JFrame frame = new JFrame("ScribbleDragAndDrop"); + final ScribbleDragAndDrop scribblePane = new ScribbleDragAndDrop(); + frame.getContentPane().add(scribblePane, BorderLayout.CENTER); + + // Create two buttons for switching modes + JToolBar toolbar = new JToolBar(); + ButtonGroup group = new ButtonGroup(); + JToggleButton draw = new JToggleButton("Draw"); + JToggleButton drag = new JToggleButton("Drag"); + draw.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + scribblePane.setDragMode(false); + } + }); + drag.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + scribblePane.setDragMode(true); + } + }); + group.add(draw); group.add(drag); + toolbar.add(draw); toolbar.add(drag); + frame.getContentPane().add(toolbar, BorderLayout.NORTH); + + // Start off in drawing mode + draw.setSelected(true); + scribblePane.setDragMode(false); + + // Pop up the window + frame.setSize(400, 400); + frame.setVisible(true); + } +} diff --git a/src/main/java/com/davidflanagan/examples/datatransfer/SimpleCutAndPaste.java b/src/main/java/com/davidflanagan/examples/datatransfer/SimpleCutAndPaste.java new file mode 100644 index 0000000..874e30d --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/datatransfer/SimpleCutAndPaste.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.datatransfer; +import java.awt.*; +import java.awt.event.*; +import java.awt.datatransfer.*; + +/** + * This program demonstrates how to add simple copy-and-paste capabilities + * to an application. + **/ +public class SimpleCutAndPaste extends Frame implements ClipboardOwner +{ + /** The main method creates a frame and pops it up. */ + public static void main(String[] args) { + Frame f = new SimpleCutAndPaste(); + f.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { System.exit(0); } + }); + f.pack(); + f.setVisible(true); + } + + /** The text field that holds the text that is cut or pasted */ + TextField field; + + /** + * The constructor builds a very simple test GUI, and registers this object + * as the ActionListener for the buttons + **/ + public SimpleCutAndPaste() { + super("SimpleCutAndPaste"); // Window title + this.setFont(new Font("SansSerif", Font.PLAIN, 18)); // Use a big font + + // Set up the Cut button + Button copy = new Button("Copy"); + copy.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { copy(); } + }); + this.add(copy, "West"); + + // Set up the Paste button + Button paste = new Button("Paste"); + paste.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { paste(); } + }); + this.add(paste, "East"); + + // Set up the text field that they both operate on + field = new TextField(); + this.add(field, "North"); + } + + /** + * This method takes the current contents of the text field, creates a + * StringSelection object to represent that string, and puts the + * StringSelection onto the clipboard + **/ + public void copy() { + // Get the currently displayed value + String s = field.getText(); + + // Create a StringSelection object to represent the text. + // StringSelection is a pre-defined class that implements + // Transferable and ClipboardOwner for us. + StringSelection ss = new StringSelection(s); + + // Now set the StringSelection object as the contents of the clipboard + // Also specify that we're the clipboard owner + this.getToolkit().getSystemClipboard().setContents(ss, this); + + // Highlight the text to indicate it is on the clipboard. + field.selectAll(); + } + + /** + * Get the contents of the clipboard, and, if we understand the type, + * display the contents. This method understands strings and file lists. + **/ + public void paste() { + // Get the clipboard + Clipboard c = this.getToolkit().getSystemClipboard(); + + // Get the contents of the clipboard, as a Transferable object + Transferable t = c.getContents(this); + + // Find out what kind of data is on the clipboard + try { + if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) { + // If it is a string, then get and display the string + String s = (String) t.getTransferData(DataFlavor.stringFlavor); + field.setText(s); + + } + else if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + // If it is a list of File objects, get the list and display + // the name of the first file on the list + java.util.List files = (java.util.List) + t.getTransferData(DataFlavor.javaFileListFlavor); + java.io.File file = (java.io.File)files.get(0); + field.setText(file.getName()); + } + } + // If anything goes wrong with the transfer, just beep and do nothing. + catch (Exception e) { this.getToolkit().beep(); } + } + + /** + * This method implements the ClipboardOwner interface. It is called when + * something else is placed on the clipboard. + **/ + public void lostOwnership(Clipboard c, Transferable t) { + // Un-highlight the text field, since we don't "own" the clipboard + // anymore, and the text is no longer available to be pasted. + field.select(0,0); + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/AntiAlias.java b/src/main/java/com/davidflanagan/examples/graphics/AntiAlias.java new file mode 100644 index 0000000..dc5effa --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/AntiAlias.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.*; + +/** A demonstration of anti-aliasing */ +public class AntiAlias implements GraphicsExample { + static final int WIDTH = 650, HEIGHT = 350; // Size of our example + public String getName() {return "AntiAliasing";} // From GraphicsExample + public int getWidth() { return WIDTH; } // From GraphicsExample + public int getHeight() { return HEIGHT; } // From GraphicsExample + + /** Draw the example */ + public void draw(Graphics2D g, Component c) { + BufferedImage image = // Create an off-screen image + new BufferedImage(65, 35, BufferedImage.TYPE_INT_RGB); + Graphics2D ig = image.createGraphics(); // Get its Graphics for drawing + + // Set the background to a gradient fill. The varying color of + // the background helps to demonstrate the anti-aliasing effect + ig.setPaint(new GradientPaint(0,0,Color.black,65,35,Color.white)); + ig.fillRect(0, 0, 65, 35); + + // Set drawing attributes for the foreground. + // Most importantly, turn on anti-aliasing. + ig.setStroke(new BasicStroke(2.0f)); // 2-pixel lines + ig.setFont(new Font("Serif", Font.BOLD, 18)); // 18-point font + ig.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // Anti-alias! + RenderingHints.VALUE_ANTIALIAS_ON); + + // Now draw pure blue text and a pure red oval + ig.setColor(Color.blue); + ig.drawString("Java", 9, 22); + ig.setColor(Color.red); + ig.drawOval(1, 1, 62, 32); + + // Finally, scale the image by a factor of 10 and display it + // in the window. This will allow us to see the anti-aliased pixels + g.drawImage(image, AffineTransform.getScaleInstance(10, 10), c); + + // Draw the image one more time at its original size, for comparison + g.drawImage(image, 0, 0, c); + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.html b/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.html new file mode 100644 index 0000000..bf650dd --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.html @@ -0,0 +1,6 @@ + + + + diff --git a/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.java b/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.java new file mode 100644 index 0000000..93801d4 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/BouncingCircle.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.applet.*; +import java.awt.*; + +/** An applet that displays a simple animation */ +public class BouncingCircle extends Applet implements Runnable { + int x = 150, y = 50, r = 50; // Position and radius of the circle + int dx = 11, dy = 7; // Trajectory of circle + Thread animator; // The thread that performs the animation + volatile boolean pleaseStop; // A flag to ask the thread to stop + + /** This method simply draws the circle at its current position */ + public void paint(Graphics g) { + g.setColor(Color.red); + g.fillOval(x-r, y-r, r*2, r*2); + } + + /** + * This method moves (and bounces) the circle and then requests a redraw. + * The animator thread calls this method periodically. + **/ + public void animate() { + // Bounce if we've hit an edge. + Rectangle bounds = getBounds(); + if ((x - r + dx < 0) || (x + r + dx > bounds.width)) dx = -dx; + if ((y - r + dy < 0) || (y + r + dy > bounds.height)) dy = -dy; + + // Move the circle. + x += dx; y += dy; + + // Ask the browser to call our paint() method to draw the circle + // at its new position. + repaint(); + } + + /** + * This method is from the Runnable interface. It is the body of the + * thread that performs the animation. The thread itself is created + * and started in the start() method. + **/ + public void run() { + while(!pleaseStop) { // Loop until we're asked to stop + animate(); // Update and request redraw + try { Thread.sleep(100); } // Wait 100 milliseconds + catch(InterruptedException e) {} // Ignore interruptions + } + } + + /** Start animating when the browser starts the applet */ + public void start() { + animator = new Thread(this); // Create a thread + pleaseStop = false; // Don't ask it to stop now + animator.start(); // Start the thread. + // The thread that called start now returns to its caller. + // Meanwhile, the new animator thread has called the run() method + } + + /** Stop animating when the browser stops the applet */ + public void stop() { + // Set the flag that causes the run() method to end + pleaseStop = true; + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.html b/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.html new file mode 100644 index 0000000..0df1926 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.html @@ -0,0 +1,5 @@ + + + + diff --git a/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.java b/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.java new file mode 100644 index 0000000..be2d513 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/ColorGradient.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.applet.*; +import java.awt.*; + +/** An applet that demonstrates the Color class */ +public class ColorGradient extends Applet { + Color startColor, endColor; // Start and end color of the gradient + Font bigFont; // A font we'll use + + /** + * Get the gradient start and end colors as applet parameter values, and + * parse them using Color.decode(). If they are malformed, use white. + **/ + public void init() { + try { + startColor = Color.decode(getParameter("startColor")); + endColor = Color.decode(getParameter("endColor")); + } + catch (NumberFormatException e) { + startColor = endColor = Color.white; + } + bigFont = new Font("Helvetica", Font.BOLD, 72); + } + + /** Draw the applet. The interesting code is in fillGradient() below */ + public void paint(Graphics g) { + fillGradient(this, g, startColor, endColor); // display the gradient + g.setFont(bigFont); // set a font + g.setColor(new Color(100, 100, 200)); // light blue + g.drawString("Colors!", 100, 100); // draw something interesting + } + + /** + * Draw a color gradient from the top of the specified component to the + * bottom. Start with the start color and change smoothly to the end + **/ + public void fillGradient(Component c, Graphics g, Color start, Color end) { + Rectangle bounds = this.getBounds(); // How big is the component? + // Get the red, green, and blue components of the start and end + // colors as floats between 0.0 and 1.0. Note that the Color class + // also works with int values between 0 and 255 + float r1 = start.getRed()/255.0f; + float g1 = start.getGreen()/255.0f; + float b1 = start.getBlue()/255.0f; + float r2 = end.getRed()/255.0f; + float g2 = end.getGreen()/255.0f; + float b2 = end.getBlue()/255.0f; + // Figure out how much each component should change at each y value + float dr = (r2-r1)/bounds.height; + float dg = (g2-g1)/bounds.height; + float db = (b2-b1)/bounds.height; + + // Now loop once for each row of pixels in the component + for(int y = 0; y < bounds.height; y++) { + g.setColor(new Color(r1, g1, b1)); // Set the color of the row + g.drawLine(0, y, bounds.width-1, y); // Draw the row + r1 += dr; g1 += dg; b1 += db; // Increment color components + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/CompositeEffects.java b/src/main/java/com/davidflanagan/examples/graphics/CompositeEffects.java new file mode 100644 index 0000000..eb7a7b1 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/CompositeEffects.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.*; + +public class CompositeEffects implements GraphicsExample { + Image cover; // The image we'll be displaying, and its size + static final int COVERWIDTH = 127, COVERHEIGHT = 190; + + /** This constructor loads the cover image */ + public CompositeEffects() { + java.net.URL imageurl = this.getClass().getResource("cover.gif"); + cover = new javax.swing.ImageIcon(imageurl).getImage(); + } + + // These are basic GraphicsExample methods + public String getName() {return "Composite Effects";} + public int getWidth() { return 6*COVERWIDTH + 70; } + public int getHeight() { return COVERHEIGHT + 35; } + + /** Draw the example */ + public void draw(Graphics2D g, Component c) { + // fill the background + g.setPaint(new Color(175, 175, 175)); + g.fillRect(0, 0, getWidth(), getHeight()); + + // Set text attributes + g.setColor(Color.black); + g.setFont(new Font("SansSerif", Font.BOLD, 12)); + + // Draw the unmodified image + g.translate(10, 10); + g.drawImage(cover, 0, 0, c); + g.drawString("SRC_OVER", 0, COVERHEIGHT+15); + + // Draw the cover again, using AlphaComposite to make the opaque + // colors of the image 50% translucent + g.translate(COVERWIDTH+10, 0); + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + 0.5f)); + g.drawImage(cover, 0, 0, c); + + // Restore the pre-defined default Composite for the screen, so + // opaque colors stay opaque. + g.setComposite(AlphaComposite.SrcOver); + // Label the effect + g.drawString("SRC_OVER, 50%", 0, COVERHEIGHT+15); + + // Now get an offscreen image to work with. In order to achieve + // certain compositing effects, the drawing surface must support + // transparency. Onscreen drawing surfaces cannot, so we have to do the + // compositing in an offscreen image that is specially created to have + // an "alpha channel", then copy the final result to the screen. + BufferedImage offscreen = + new BufferedImage(COVERWIDTH, COVERHEIGHT, + BufferedImage.TYPE_INT_ARGB); + + // First, fill the image with a color gradient background that varies + // left-to-right from opaque to transparent yellow + Graphics2D osg = offscreen.createGraphics(); + osg.setPaint(new GradientPaint(0, 0, Color.yellow, + COVERWIDTH, 0, + new Color(255, 255, 0, 0))); + osg.fillRect(0,0, COVERWIDTH, COVERHEIGHT); + + // Now copy the cover image on top of this, but use the DstOver rule + // which draws it "underneath" the existing pixels, and allows the + // image to show depending on the transparency of those pixels. + osg.setComposite(AlphaComposite.DstOver); + osg.drawImage(cover, 0, 0, c); + + // And display this composited image on the screen. Note that the + // image is opaque and that none of the screen background shows through + g.translate(COVERWIDTH+10, 0); + g.drawImage(offscreen, 0, 0, c); + g.drawString("DST_OVER", 0, COVERHEIGHT+15); + + // Now start over and do a new effect with the off-screen image. + // First, fill the offscreen image with a new color gradient. We + // don't care about the colors themselves; we just want the + // translucency of the background to vary. We use opaque black to + // transparent black. Note that since we've already used this offscreen + // image, we set the composite to Src, we can fill the image and + // ignore anything that is already there. + osg.setComposite(AlphaComposite.Src); + osg.setPaint(new GradientPaint(0, 0, Color.black, + COVERWIDTH, COVERHEIGHT, + new Color(0, 0, 0, 0))); + osg.fillRect(0,0, COVERWIDTH, COVERHEIGHT); + + // Now set the compositing type to SrcIn, so colors come from the + // source, but translucency comes from the destination + osg.setComposite(AlphaComposite.SrcIn); + + // Draw our loaded image into the off-screen image, compositing it. + osg.drawImage(cover, 0, 0, c); + + // And then copy our off-screen image to the screen. Note that the + // image is translucent and some of the image shows through. + g.translate(COVERWIDTH+10, 0); + g.drawImage(offscreen, 0, 0, c); + g.drawString("SRC_IN", 0, COVERHEIGHT+15); + + // If we do the same thing but use SrcOut, then the resulting image + // will have the inverted translucency values of the destination + osg.setComposite(AlphaComposite.Src); + osg.setPaint(new GradientPaint(0, 0, Color.black, + COVERWIDTH, COVERHEIGHT, + new Color(0, 0, 0, 0))); + osg.fillRect(0,0, COVERWIDTH, COVERHEIGHT); + osg.setComposite(AlphaComposite.SrcOut); + osg.drawImage(cover, 0, 0, c); + g.translate(COVERWIDTH+10, 0); + g.drawImage(offscreen, 0, 0, c); + g.drawString("SRC_OUT", 0, COVERHEIGHT+15); + + // Here's a cool effect; it has nothing to do with compositing, but + // uses an arbitrary shape to clip the image. It uses Area to combine + // shapes into more complicated ones. + g.translate(COVERWIDTH+10, 0); + Shape savedClip = g.getClip(); // Save current clipping region + // Create a shape to use as the new clipping region. + // Begin with an ellipse + Area clip = new Area(new Ellipse2D.Float(0,0,COVERWIDTH,COVERHEIGHT)); + // Intersect with a rectangle, truncating the ellipse. + clip.intersect(new Area(new Rectangle(5,5, + COVERWIDTH-10,COVERHEIGHT-10))); + // Then subtract an ellipse from the bottom of the truncated ellipse. + clip.subtract(new Area(new Ellipse2D.Float(COVERWIDTH/2-40, + COVERHEIGHT-20, 80, 40))); + // Use the resulting shape as the new clipping region + g.clip(clip); + // Then draw the image through this clipping region + g.drawImage(cover, 0, 0, c); + // Restore the old clipping region so we can label the effect + g.setClip(savedClip); + g.drawString("Clipping", 0, COVERHEIGHT+15); + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/CustomStrokes.java b/src/main/java/com/davidflanagan/examples/graphics/CustomStrokes.java new file mode 100644 index 0000000..20a59ef --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/CustomStrokes.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; +import java.awt.font.*; + +/** A demonstration of writing custom Stroke classes */ +public class CustomStrokes implements GraphicsExample { + static final int WIDTH = 750, HEIGHT = 200; // Size of our example + public String getName() {return "Custom Strokes";} // From GraphicsExample + public int getWidth() { return WIDTH; } // From GraphicsExample + public int getHeight() { return HEIGHT; } // From GraphicsExample + + // These are the various stroke objects we'll demonstrate + Stroke[] strokes = new Stroke[] { + new BasicStroke(4.0f), // The standard, predefined stroke + new NullStroke(), // A Stroke that does nothing + new DoubleStroke(8.0f, 2.0f), // A Stroke that strokes twice + new ControlPointsStroke(2.0f), // Shows the vertices & control points + new SloppyStroke(2.0f, 3.0f) // Perturbs the shape before stroking + }; + + /** Draw the example */ + public void draw(Graphics2D g, Component c) { + // Get a shape to work with. Here we'll use the letter B + Font f = new Font("Serif", Font.BOLD, 200); + GlyphVector gv = f.createGlyphVector(g.getFontRenderContext(), "B"); + Shape shape = gv.getOutline(); + + // Set drawing attributes and starting position + g.setColor(Color.black); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g.translate(10, 175); + + // Draw the shape once with each stroke + for(int i = 0; i < strokes.length; i++) { + g.setStroke(strokes[i]); // set the stroke + g.draw(shape); // draw the shape + g.translate(140,0); // move to the right + } + } +} + +/** + * This Stroke implementation does nothing. Its createStrokedShape() + * method returns an unmodified shape. Thus, drawing a shape with + * this Stroke is the same as filling that shape! + **/ +class NullStroke implements Stroke { + public Shape createStrokedShape(Shape s) { return s; } +} + +/** + * This Stroke implementation applies a BasicStroke to a shape twice. + * If you draw with this Stroke, then instead of outlining the shape, + * you're outlining the outline of the shape. + **/ +class DoubleStroke implements Stroke { + BasicStroke stroke1, stroke2; // the two strokes to use + public DoubleStroke(float width1, float width2) { + stroke1 = new BasicStroke(width1); // Constructor arguments specify + stroke2 = new BasicStroke(width2); // the line widths for the strokes + } + + public Shape createStrokedShape(Shape s) { + // Use the first stroke to create an outline of the shape + Shape outline = stroke1.createStrokedShape(s); + // Use the second stroke to create an outline of that outline. + // It is this outline of the outline that will be filled in + return stroke2.createStrokedShape(outline); + } +} + +/** + * This Stroke implementation strokes the shape using a thin line, and + * also displays the end points and Bezier curve control points of all + * the line and curve segments that make up the shape. The radius + * argument to the constructor specifies the size of the control point + * markers. Note the use of PathIterator to break the shape down into + * its segments, and of GeneralPath to build up the stroked shape. + **/ +class ControlPointsStroke implements Stroke { + float radius; // how big the control point markers should be + public ControlPointsStroke(float radius) { this.radius = radius; } + + public Shape createStrokedShape(Shape shape) { + // Start off by stroking the shape with a thin line. Store the + // resulting shape in a GeneralPath object so we can add to it. + GeneralPath strokedShape = + new GeneralPath(new BasicStroke(1.0f).createStrokedShape(shape)); + + // Use a PathIterator object to iterate through each of the line and + // curve segments of the shape. For each one, mark the endpoint and + // control points (if any) by adding a rectangle to the GeneralPath + float[] coords = new float[6]; + for(PathIterator i=shape.getPathIterator(null); !i.isDone();i.next()) { + int type = i.currentSegment(coords); + Shape s = null, s2 = null, s3 = null; + switch(type) { + case PathIterator.SEG_CUBICTO: + markPoint(strokedShape, coords[4], coords[5]); // falls through + case PathIterator.SEG_QUADTO: + markPoint(strokedShape, coords[2], coords[3]); // falls through + case PathIterator.SEG_MOVETO: + case PathIterator.SEG_LINETO: + markPoint(strokedShape, coords[0], coords[1]); // falls through + case PathIterator.SEG_CLOSE: + break; + } + } + + return strokedShape; + } + + /** Add a small square centered at (x,y) to the specified path */ + void markPoint(GeneralPath path, float x, float y) { + path.moveTo(x-radius, y-radius); // Begin a new sub-path + path.lineTo(x+radius, y-radius); // Add a line segment to it + path.lineTo(x+radius, y+radius); // Add a second line segment + path.lineTo(x-radius, y+radius); // And a third + path.closePath(); // Go back to last moveTo position + } +} + +/** + * This Stroke implementation randomly perturbs the line and curve segments + * that make up a Shape, and then strokes that perturbed shape. It uses + * PathIterator to loop through the Shape and GeneralPath to build up the + * modified shape. Finally, it uses a BasicStroke to stroke the modified + * shape. The result is a "sloppy" looking shape. + **/ +class SloppyStroke implements Stroke { + BasicStroke stroke; + float sloppiness; + public SloppyStroke(float width, float sloppiness) { + this.stroke = new BasicStroke(width); // Used to stroke modified shape + this.sloppiness = sloppiness; // How sloppy should we be? + } + + public Shape createStrokedShape(Shape shape) { + GeneralPath newshape = new GeneralPath(); // Start with an empty shape + + // Iterate through the specified shape, perturb its coordinates, and + // use them to build up the new shape. + float[] coords = new float[6]; + for(PathIterator i=shape.getPathIterator(null); !i.isDone();i.next()) { + int type = i.currentSegment(coords); + switch(type) { + case PathIterator.SEG_MOVETO: + perturb(coords, 2); + newshape.moveTo(coords[0], coords[1]); + break; + case PathIterator.SEG_LINETO: + perturb(coords, 2); + newshape.lineTo(coords[0], coords[1]); + break; + case PathIterator.SEG_QUADTO: + perturb(coords, 4); + newshape.quadTo(coords[0], coords[1], coords[2], coords[3]); + break; + case PathIterator.SEG_CUBICTO: + perturb(coords, 6); + newshape.curveTo(coords[0], coords[1], coords[2], coords[3], + coords[4], coords[5]); + break; + case PathIterator.SEG_CLOSE: + newshape.closePath(); + break; + } + } + + // Finally, stroke the perturbed shape and return the result + return stroke.createStrokedShape(newshape); + } + + // Randomly modify the specified number of coordinates, by an amount + // specified by the sloppiness field. + void perturb(float[] coords, int numCoords) { + for(int i = 0; i < numCoords; i++) + coords[i] += (float)((Math.random()*2-1.0)*sloppiness); + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/FontList.html b/src/main/java/com/davidflanagan/examples/graphics/FontList.html new file mode 100644 index 0000000..b4e58da --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/FontList.html @@ -0,0 +1,3 @@ + + diff --git a/src/main/java/com/davidflanagan/examples/graphics/FontList.java b/src/main/java/com/davidflanagan/examples/graphics/FontList.java new file mode 100644 index 0000000..e002eec --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/FontList.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.applet.*; +import java.awt.*; + +/** + * An applet that displays the standard fonts and styles available in Java 1.1 + **/ +public class FontList extends Applet { + // The available font families + String[] families = {"Serif", // "TimesRoman" in Java 1.0 + "SansSerif", // "Helvetica" in Java 1.0 + "Monospaced"}; // "Courier" in Java 1.0 + + // The available font styles and names for each one + int[] styles = {Font.PLAIN, Font.ITALIC, Font.BOLD, Font.ITALIC+Font.BOLD}; + String[] stylenames = {"Plain", "Italic", "Bold", "Bold Italic"}; + + // Draw the applet. + public void paint(Graphics g) { + for(int f=0; f < families.length; f++) { // for each family + for(int s = 0; s < styles.length; s++) { // for each style + Font font = new Font(families[f],styles[s],18); // create font + g.setFont(font); // set font + String name = families[f] + " " +stylenames[s]; // create name + g.drawString(name, 20, (f*4 + s + 1) * 20); // display name + } + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/GenericPaint.java b/src/main/java/com/davidflanagan/examples/graphics/GenericPaint.java new file mode 100644 index 0000000..34214a0 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/GenericPaint.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.*; + +/** + * This is an abstract Paint implementation that computes the color of each + * point to be painted by passing the coordinates of the point to the calling + * the abstract methods computeRed(), computeGreen(), computeBlue() and + * computeAlpha(). Subclasses must implement these three methods to perform + * whatever type of painting is desired. Note that while this class provides + * great flexibility, it is not very efficient. + **/ +public abstract class GenericPaint implements Paint { + /** This is the main Paint method; all it does is return a PaintContext */ + public PaintContext createContext(ColorModel cm, + Rectangle deviceBounds, + Rectangle2D userBounds, + AffineTransform xform, + RenderingHints hints) { + return new GenericPaintContext(xform); + } + + /** This paint class allows translucent painting */ + public int getTransparency() { return TRANSLUCENT; } + + /** + * These three methods return the red, green, blue, and alpha values of + * the pixel at appear at the specified user-space coordinates. The return + * value of each method should be between 0 and 255. + **/ + public abstract int computeRed(double x, double y); + public abstract int computeGreen(double x, double y); + public abstract int computeBlue(double x, double y); + public abstract int computeAlpha(double x, double y); + + /** + * The PaintContext class does all the work of painting + **/ + class GenericPaintContext implements PaintContext { + ColorModel model; // The color model + Point2D origin, unitVectorX, unitVectorY; // For device-to-user xform + + public GenericPaintContext(AffineTransform userToDevice) { + // Our color model packs ARGB values into a single int + model = new DirectColorModel(32, 0x00ff0000,0x0000ff00, + 0x000000ff, 0xff000000); + // The specified transform converts user to device pixels + // We need to figure out the reverse transformation, so we + // can compute the user space coordinates of each device pixel + try { + AffineTransform deviceToUser = userToDevice.createInverse(); + origin = deviceToUser.transform(new Point(0,0), null); + unitVectorX = deviceToUser.deltaTransform(new Point(1,0),null); + unitVectorY = deviceToUser.deltaTransform(new Point(0,1),null); + } + catch (NoninvertibleTransformException e) { + // If we can't invert the transform, just use device space + origin = new Point(0,0); + unitVectorX = new Point(1,0); + unitVectorY = new Point(0, 1); + } + } + + /** Return the color model used by this Paint implementation */ + public ColorModel getColorModel() { return model; } + + /** + * This is the main method of PaintContext. It must return a Raster + * that contains fill data for the specified rectangle. It creates a + * raster of the specified size, and loops through the device pixels. + * For each one, it converts the coordinates to user space, then calls + * the computeRed(), computeGreen() and computeBlue() methods to + * obtain the appropriate color for the device pixel. + **/ + public Raster getRaster(int x, int y, int w, int h) { + WritableRaster raster = model.createCompatibleWritableRaster(w,h); + int[] colorComponents = new int[4]; + for(int j = 0; j < h; j++) { // Loop through rows of raster + int deviceY = y + j; + for(int i = 0; i < w; i++) { // Loop through columns + int deviceX = x + i; + // Convert device coordinate to user-space coordinate + double userX = origin.getX() + + deviceX * unitVectorX.getX() + + deviceY * unitVectorY.getX(); + double userY = origin.getY() + + deviceX * unitVectorX.getY() + + deviceY * unitVectorY.getY(); + // Compute the color components of the pixel + colorComponents[0] = computeRed(userX, userY); + colorComponents[1] = computeGreen(userX, userY); + colorComponents[2] = computeBlue(userX, userY); + colorComponents[3] = computeAlpha(userX, userY); + // Set the color of the pixel + raster.setPixel(i, j, colorComponents); + } + } + return raster; + } + + /** Called when the PaintContext is no longer needed. */ + public void dispose() {} + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/GraphicsExample.java b/src/main/java/com/davidflanagan/examples/graphics/GraphicsExample.java new file mode 100644 index 0000000..e8abc3d --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/GraphicsExample.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; + +/** + * This interface defines the methods that must be implemented by an + * object that is to be displayed by the GraphicsExampleFrame object + */ +public interface GraphicsExample { + public String getName(); // Return the example name + public int getWidth(); // Return its width + public int getHeight(); // Return its height + public void draw(Graphics2D g, Component c); // Draw the example +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/GraphicsExampleFrame.java b/src/main/java/com/davidflanagan/examples/graphics/GraphicsExampleFrame.java new file mode 100644 index 0000000..3ee011b --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/GraphicsExampleFrame.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.awt.print.*; + +/** + * This class displays one or more GraphicsExample objects in a + * Swing JFrame and a JTabbedPane + */ +public class GraphicsExampleFrame extends JFrame { + public GraphicsExampleFrame(final GraphicsExample[] examples) { + super("GraphicsExampleFrame"); + + Container cpane = getContentPane(); // Set up the frame + cpane.setLayout(new BorderLayout()); + final JTabbedPane tpane = new JTabbedPane(); // And the tabbed pane + cpane.add(tpane, BorderLayout.CENTER); + + // Add a menubar + JMenuBar menubar = new JMenuBar(); // Create the menubar + this.setJMenuBar(menubar); // Add it to the frame + JMenu filemenu = new JMenu("File"); // Create a File menu + menubar.add(filemenu); // Add to the menubar + JMenuItem print = new JMenuItem("Print"); // Create a Print item + filemenu.add(print); // Add it to the menu + JMenuItem quit = new JMenuItem("Quit"); // Create a Quit item + filemenu.add(quit); // Add it to the menu + + // Tell the Print menu item what to do when selected + print.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Get the currently displayed example, and call + // the print method (defined below) + print(examples[tpane.getSelectedIndex()]); + } + }); + + // Tell the Quit menu item what to do when selected + quit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { System.exit(0); } + }); + + // In addition to the Quit menu item, also handle window close events + this.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { System.exit(0); } + }); + + // Insert each of the example objects into the tabbed pane + for(int i = 0; i < examples.length; i++) { + GraphicsExample e = examples[i]; + tpane.addTab(e.getName(), new GraphicsExamplePane(e)); + } + } + + /** + * This inner class is a custom Swing component that displays + * a GraphicsExample object. + */ + public class GraphicsExamplePane extends JComponent { + GraphicsExample example; // The example to display + Dimension size; // How much space it requires + + public GraphicsExamplePane(GraphicsExample example) { + this.example = example; + size = new Dimension(example.getWidth(), example.getHeight()); + } + + /** Draw the component and the example it contains */ + public void paintComponent(Graphics g) { + g.setColor(Color.white); // set the background + g.fillRect(0, 0, size.width, size.height); // to white + g.setColor(Color.black); // set a default drawing color + example.draw((Graphics2D) g, this); // ask example to draw itself + } + + // These methods specify how big the component must be + public Dimension getPreferredSize() { return size; } + public Dimension getMinimumSize() { return size; } + } + + /** This method is invoked by the Print menu item */ + public void print(final GraphicsExample example) { + // Start off by getting a printer job to do the printing + PrinterJob job = PrinterJob.getPrinterJob(); + // Wrap the example in a Printable object (defined below) + // and tell the PrinterJob that we want to print it + job.setPrintable(new PrintableExample(example)); + + // Display the print dialog to the user + if (job.printDialog()) { + // If they didn't cancel it, then tell the job to start printing + try { + job.print(); + } + catch(PrinterException e) { + System.out.println("Couldn't print: " + e.getMessage()); + } + } + } + + /** + * This inner class implements the Printable interface in order to print + * a GraphicsExample object. + **/ + class PrintableExample implements Printable { + GraphicsExample example; // The example to print + + // The constructor. Just remember the example + public PrintableExample(GraphicsExample example) { + this.example = example; + } + + /** + * This method is called by the PrinterJob to print the example + **/ + public int print(Graphics g, PageFormat pf, int pageIndex) { + // Tell the PrinterJob that there is only one page + if (pageIndex != 0) return NO_SUCH_PAGE; + + // The PrinterJob supplies us a Graphics object to draw with. + // Anything drawn with this object will be sent to the printer. + // The Graphics object can safely be cast to a Graphics2D object. + Graphics2D g2 = (Graphics2D)g; + + // Translate to skip the left and top margins. + g2.translate(pf.getImageableX(), pf.getImageableY()); + + // Figure out how big the printable area is, and how big + // the example is. + double pageWidth = pf.getImageableWidth(); + double pageHeight = pf.getImageableHeight(); + double exampleWidth = example.getWidth(); + double exampleHeight = example.getHeight(); + + // Scale the example if needed + double scalex = 1.0, scaley = 1.0; + if (exampleWidth > pageWidth) scalex = pageWidth/exampleWidth; + if (exampleHeight > pageHeight) scaley = pageHeight/exampleHeight; + double scalefactor = Math.min(scalex, scaley); + if (scalefactor != 1) g2.scale(scalefactor, scalefactor); + + // Finally, call the draw() method of the example, passing in + // the Graphics2D object for the printer + example.draw(g2, GraphicsExampleFrame.this); + + // Tell the PrinterJob that we successfully printed the page + return PAGE_EXISTS; + } + } + + /** + * The main program. Use Java reflection to load and instantiate + * the specified GraphicsExample classes, then create a + * GraphicsExampleFrame to display them. + **/ + public static void main(String[] args) { + GraphicsExample[] examples = new GraphicsExample[args.length]; + + // Loop through the command line arguments + for(int i=0; i < args.length; i++) { + // The class name of the requested example + String classname = args[i]; + + // If no package is specified, assume it is in this package + if (classname.indexOf('.') == -1) + classname = "com.davidflanagan.examples.graphics."+args[i]; + + // Try to instantiate the named GraphicsExample class + try { + Class exampleClass = Class.forName(classname); + examples[i] = (GraphicsExample) exampleClass.newInstance(); + } + catch (ClassNotFoundException e) { // unknown class + System.err.println("Couldn't find example: " + classname); + System.exit(1); + } + catch (ClassCastException e) { // wrong type of class + System.err.println("Class " + classname + + " is not a GraphicsExample"); + System.exit(1); + } + catch (Exception e) { // class doesn't have a public constructor + // catch InstantiationException, IllegalAccessException + System.err.println("Couldn't instantiate example: " + + classname); + System.exit(1); + } + } + + // Now create a window to display the examples in, and make it visible + GraphicsExampleFrame f = new GraphicsExampleFrame(examples); + f.pack(); + f.setVisible(true); + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.html b/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.html new file mode 100644 index 0000000..5edd6c7 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.html @@ -0,0 +1,4 @@ + + diff --git a/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.java b/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.java new file mode 100644 index 0000000..a310a2e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/GraphicsSampler.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.applet.*; +import java.awt.*; + +/** + * An applet that demonstrates most of the graphics primitives in + * java.awt.Graphics. + **/ +public class GraphicsSampler extends Applet { + Color fill, outline, textcolor; // The various colors we use + Font font; // The font we use for text + FontMetrics metrics; // Information about font size + Image image, background; // Some images we draw with + + // This method is called when the applet is first created. + // It performs initialization, such as creating the resources + // (graphics attribute values) used by the paint() method. + public void init() { + // Initialize color resources. Note the use of the Color() constructor + // and the use of pre-defined color constants. + fill = new Color(200, 200, 200); // Equal red, green, and blue == gray + outline = Color.blue; // Same as new Color(0, 0, 255) + textcolor = Color.red; // Same as new Color(255, 0, 0) + + // Create a font for use in the paint() method. Get its metrics, too. + font = new Font("sansserif", Font.BOLD, 14); + metrics = this.getFontMetrics(font); + + // Load some Image objects for use in the paint() method. + image = this.getImage(this.getDocumentBase(), "tiger.gif"); + background = this.getImage(this.getDocumentBase(), "background.gif"); + + // Set a property that tells the applet its background color + this.setBackground(Color.lightGray); + } + + // This method is called whenever the applet needs to be drawn or redrawn + public void paint(Graphics g) { + g.setFont(font); // Specify the font we'll be using throughout + + // Draw a background by tiling an image tile() is defined below + tile(g, this, background); + + // Draw a line + g.setColor(outline); // Specify the drawing color + g.drawLine(25, 10, 150, 80); // Draw a line from (25,10) to (150,80) + // Draw some text. See the centerText() method below. + centerText("drawLine()", null, g, textcolor, 25, 10, 150, 80); + + // Draw and fill an arc + g.setColor(fill); + g.fillArc(225, 10, 150, 80, 90, 135); + g.setColor(outline); + g.drawArc(225, 10, 150, 80, 90, 135); + centerText("fillArc()", "drawArc()", g, textcolor,225,10,150,80); + + // Draw and fill a rectangle + g.setColor(fill); + g.fillRect(25, 110, 150, 80); + g.setColor(outline); + g.drawRect(25, 110, 150, 80); + centerText("fillRect()", "drawRect()", g, textcolor, 25, 110, 150, 80); + + // Draw and fill a rounded rectangle + g.setColor(fill); + g.fillRoundRect(225, 110, 150, 80, 20, 20); + g.setColor(outline); + g.drawRoundRect(225, 110, 150, 80, 20, 20); + centerText("fillRoundRect()", "drawRoundRect()", g, textcolor, + 225, 110, 150, 80); + + // Draw and fill an oval + g.setColor(fill); + g.fillOval(25, 210, 150, 80); + g.setColor(outline); + g.drawOval(25, 210, 150, 80); + centerText("fillOval()", "drawOval()", g, textcolor, 25, 210, 150, 80); + + // Define an octagon using arrays of X and Y coordinates + int numpoints = 8; + int[] xpoints = new int[numpoints+1]; + int[] ypoints = new int[numpoints+1]; + for(int i=0; i < numpoints; i++) { + double angle = 2*Math.PI * i / numpoints; + xpoints[i] = (int)(300 + 75*Math.cos(angle)); + ypoints[i] = (int)(250 - 40*Math.sin(angle)); + } + + // Draw and fill the polygon + g.setColor(fill); + g.fillPolygon(xpoints, ypoints, numpoints); + g.setColor(outline); + g.drawPolygon(xpoints, ypoints, numpoints); + centerText("fillPolygon()", "drawPolygon()", g, textcolor, + 225, 210, 150, 80); + + // Draw a 3D rectangle (clear an area for it first) + g.setColor(fill); + g.fillRect(20, 305, 160, 90); + g.draw3DRect(25, 310, 150, 80, true); + g.draw3DRect(26, 311, 148, 78, true); + g.draw3DRect(27, 312, 146, 76, true); + centerText("draw3DRect()", "x 3", g, textcolor, 25, 310, 150, 80); + + // Draw an image (centered within an area) + int w = image.getWidth(this); + int h = image.getHeight(this); + g.drawImage(image, 225 + (150-w)/2, 310 + (80-h)/2, this); + centerText("drawImage()", null, g, textcolor, 225, 310, 150, 80); + } + + // Utility method to tile an image on the background of the component + protected void tile(Graphics g, Component c, Image i) { + // Use bounds() instead of getBounds() if you want + // compatibility with Java 1.0 and old browsers like Netscape 3 + Rectangle r = c.getBounds(); // How big is the component? + int iw = i.getWidth(c); // How big is the image? + int ih = i.getHeight(c); + if ((iw <= 0) || (ih <= 0)) return; + for(int x=0; x < r.width; x += iw) // Loop horizontally + for(int y=0; y < r.height; y += ih) // Loop vertically + g.drawImage(i, x, y, c); // Draw the image + } + + // Utility method to center two lines of text in a rectangle. + // Relies on the FontMetrics obtained in the init() method. + protected void centerText(String s1, String s2, Graphics g, Color c, + int x, int y, int w, int h) + { + int height = metrics.getHeight(); // How tall is the font? + int ascent = metrics.getAscent(); // Where is the font baseline? + int width1=0, width2 = 0, x0=0, x1=0, y0=0, y1=0; + width1 = metrics.stringWidth(s1); // How wide are the strings? + if (s2 != null) width2 = metrics.stringWidth(s2); + x0 = x + (w - width1)/2; // Center the strings horizontally + x1 = x + (w - width2)/2; + if (s2 == null) // Center one string vertically + y0 = y + (h - height)/2 + ascent; + else { // Center two strings vertically + y0 = y + (h - (int)(height * 2.2))/2 + ascent; + y1 = y0 + (int)(height * 1.2); + } + g.setColor(c); // Set the color + g.drawString(s1, x0, y0); // Draw the strings + if (s2 != null) g.drawString(s2, x1, y1); + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/Hypnosis.java b/src/main/java/com/davidflanagan/examples/graphics/Hypnosis.java new file mode 100644 index 0000000..7cb759e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/Hypnosis.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; +import javax.swing.*; +import javax.swing.Timer; // Import explicitly because of java.util.Timer + +/** + * A Swing component that smoothly animates a spiral in a hypnotic way. + **/ +public class Hypnosis extends JComponent implements ActionListener { + double x, y; // The center of the spiral + double r1, r2; // The inner and outer radii of the spiral + double a1, a2; // The start and end angles of the spiral + double deltaA; // How much the angle changes each frame + double deltaX, deltaY; // The trajectory of the center + float linewidth; // How wide the lines are + Timer timer; // The object that triggers the animation + BufferedImage buffer; // The image we use for double-buffering + Graphics2D osg; // Graphics2D object for drawing into the buffer + + public Hypnosis(double x, double y, double r1, double r2, + double a1, double a2, float linewidth, int delay, + double deltaA, double deltaX, double deltaY) + { + this.x = x; this.y = y; + this.r1 = r1; this.r2 = r2; + this.a1 = a1; this.a2 = a2; + this.linewidth = linewidth; + this.deltaA = deltaA; + this.deltaX = deltaX; + this.deltaY = deltaY; + + // Set up a timer to call actionPerformed() every delay milliseconds + timer = new Timer(delay, this); + + // Create a buffer for double-buffering + buffer = new BufferedImage((int)(2*r2+linewidth), + (int)(2*r2+linewidth), + BufferedImage.TYPE_INT_RGB); + + // Create a Graphics object for the buffer, and set the linewidth + // and request antialiasing when drawing with it + osg = buffer.createGraphics(); + osg.setStroke(new BasicStroke(linewidth, BasicStroke.CAP_ROUND, + BasicStroke.JOIN_ROUND)); + osg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } + + // Start and stop the animation by starting and stopping the timer + public void start() { timer.start(); } + public void stop() { timer.stop(); } + + /** + * Swing calls this method to ask the component to redraw itself. + * This method uses double-buffering to make the animation smoother. + * Swing does double-buffering automatically, so this may not actually + * make much difference, but it is important to understand the technique. + **/ + public void paintComponent(Graphics g) { + // Clear the background of the off-screen image + osg.setColor(getBackground()); + osg.fillRect(0, 0, buffer.getWidth(), buffer.getHeight()); + + // Now draw a black spiral into the off-screen image + osg.setColor(Color.black); + osg.draw(new Spiral(r2+linewidth/2, r2+linewidth/2, r1, a1, r2, a2)); + + // Now copy that off-screen image onto the screen + g.drawImage(buffer, (int)(x-r2), (int)(y-r2), this); + } + + /** + * This method implements the ActionListener interface. Our Timer object + * calls this method periodically. It updates the position and angles + * of the spiral and requests a redraw. Instead of redrawing the entire + * component, however, this method requests a redraw only for the + * area that has changed. + **/ + public void actionPerformed(ActionEvent e) { + // Ask to have the old bounding box of the spiral redrawn. + // Nothing else has anything drawn in it, so it doesn't need a redraw + repaint((int)(x-r2-linewidth), (int)(y-r2-linewidth), + (int)(2*(r2+linewidth)), (int)(2*(r2+linewidth))); + + // Now animate: update the position and angles of the spiral + + // Bounce if we've hit an edge + Rectangle bounds = getBounds(); + if ((x - r2 + deltaX < 0) || (x + r2 + deltaX > bounds.width)) + deltaX = -deltaX; + if ((y - r2 + deltaY < 0) || (y + r2 + deltaY > bounds.height)) + deltaY = -deltaY; + + // Move the center of the spiral + x += deltaX; + y += deltaY; + + // Increment the start and end angles; + a1 += deltaA; + a2 += deltaA; + if (a1 > 2*Math.PI) { // Don't let them get too big + a1 -= 2*Math.PI; + a2 -= 2*Math.PI; + } + + // Now ask to have the new bounding box of the spiral redrawn. This + // rectangle will be intersected with the redraw rectangle requested + // above, and only the combined region will be redrawn + repaint((int)(x-r2-linewidth), (int)(y-r2-linewidth), + (int)(2*(r2+linewidth)), (int)(2*(r2+linewidth))); + } + + /** Tell Swing not to double-buffer for us, since we do our own */ + public boolean isDoubleBuffered() { return false; } + + /** This is a main() method for testing the component */ + public static void main(String[] args) { + JFrame f = new JFrame("Hypnosis"); + Hypnosis h = new Hypnosis(200, 200, 10, 100, 0, 11*Math.PI, 7, 100, + 2*Math.PI/30, 3, 5); + f.getContentPane().add(h, BorderLayout.CENTER); + f.setSize(400, 400); + f.show(); + h.start(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/ImageOps.java b/src/main/java/com/davidflanagan/examples/graphics/ImageOps.java new file mode 100644 index 0000000..a449e53 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/ImageOps.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.*; +import java.awt.color.*; + +/** A demonstration of various image processing filters */ +public class ImageOps implements GraphicsExample { + static final int WIDTH = 600, HEIGHT = 675; // Size of our example + public String getName() {return "Image Processing";}// From GraphicsExample + public int getWidth() { return WIDTH; } // From GraphicsExample + public int getHeight() { return HEIGHT; } // From GraphicsExample + + Image image; + + /** This constructor loads the image we will manipulate */ + public ImageOps() { + java.net.URL imageurl = this.getClass().getResource("cover.gif"); + image = new javax.swing.ImageIcon(imageurl).getImage(); + } + + // These arrays of bytes are used by the LookupImageOp image filters below + static byte[] brightenTable = new byte[256]; + static byte[] thresholdTable = new byte[256]; + static { // Initialize the arrays + for(int i = 0; i < 256; i++) { + brightenTable[i] = (byte)(Math.sqrt(i/255.0)*255); + thresholdTable[i] = (byte)((i < 225)?0:i); + } + } + + // This AffineTransform is used by one of the image filters below + static AffineTransform mirrorTransform; + static { // Create and initialize the AffineTransform + mirrorTransform = AffineTransform.getTranslateInstance(127, 0); + mirrorTransform.scale(-1.0, 1.0); // flip horizontally + } + + // These are the labels we'll display for each of the filtered images + static String[] filterNames = new String[] { + "Original", "Gray Scale", "Negative", "Brighten (linear)", + "Brighten (sqrt)", "Threshold", "Blur", "Sharpen", + "Edge Detect", "Mirror", "Rotate (center)", "Rotate (lower left)" + }; + + // The following BufferedImageOp image filter objects perform + // different types of image processing operations. + static BufferedImageOp[] filters = new BufferedImageOp[] { + // 1) No filter here. We'll display the original image + null, + // 2) Convert to Grayscale color space + new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null), + // 3) Image negative. Multiply each color value by -1.0 and add 255 + new RescaleOp(-1.0f, 255f, null), + // 4) Brighten using a linear formula that increases all color values + new RescaleOp(1.25f, 0, null), + // 5) Brighten using the lookup table defined above + new LookupOp(new ByteLookupTable(0, brightenTable), null), + // 6) Threshold using the lookup table defined above + new LookupOp(new ByteLookupTable(0, thresholdTable), null), + // 7) Blur by "convolving" the image with a matrix + new ConvolveOp(new Kernel(3, 3, new float[] { + .1111f,.1111f,.1111f, + .1111f,.1111f,.1111f, + .1111f,.1111f,.1111f,})), + // 8) Sharpen by using a different matrix + new ConvolveOp(new Kernel(3, 3, new float[] { + 0.0f, -0.75f, 0.0f, + -0.75f, 4.0f, -0.75f, + 0.0f, -0.75f, 0.0f})), + // 9) Edge detect using yet another matrix + new ConvolveOp(new Kernel(3, 3, new float[] { + 0.0f, -0.75f, 0.0f, + -0.75f, 3.0f, -0.75f, + 0.0f, -0.75f, 0.0f})), + // 10) Compute a mirror image using the transform defined above + new AffineTransformOp(mirrorTransform,AffineTransformOp.TYPE_BILINEAR), + // 11) Rotate the image 180 degrees about its center point + new AffineTransformOp(AffineTransform.getRotateInstance(Math.PI,64,95), + AffineTransformOp.TYPE_NEAREST_NEIGHBOR), + // 12) Rotate the image 15 degrees about the bottom left + new AffineTransformOp(AffineTransform.getRotateInstance(Math.PI/12, + 0, 190), + AffineTransformOp.TYPE_NEAREST_NEIGHBOR), + }; + + /** Draw the example */ + public void draw(Graphics2D g, Component c) { + // Create a BufferedImage big enough to hold the Image loaded + // in the constructor. Then copy that image into the new + // BufferedImage object so that we can process it. + BufferedImage bimage = new BufferedImage(image.getWidth(c), + image.getHeight(c), + BufferedImage.TYPE_INT_RGB); + Graphics2D ig = bimage.createGraphics(); + ig.drawImage(image, 0, 0, c); // copy the image + + // Set some default graphics attributes + g.setFont(new Font("SansSerif", Font.BOLD, 12)); // 12pt bold text + g.setColor(Color.green); // Draw in green + g.translate(10, 10); // Set some margins + + // Loop through the filters + for(int i = 0; i < filters.length; i++) { + // If the filter is null, draw the original image, otherwise, + // draw the image as processed by the filter + if (filters[i] == null) g.drawImage(bimage, 0, 0, c); + else g.drawImage(filters[i].filter(bimage, null), 0, 0, c); + g.drawString(filterNames[i], 0, 205); // Label the image + g.translate(137, 0); // Move over + if (i % 4 == 3) g.translate(-137*4, 215); // Move down after 4 + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/LineStyles.java b/src/main/java/com/davidflanagan/examples/graphics/LineStyles.java new file mode 100644 index 0000000..000b4af --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/LineStyles.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; + +/** A demonstration of Java2D line styles */ +public class LineStyles implements GraphicsExample { + public String getName() { return "LineStyles"; } // From GraphicsExample + public int getWidth() { return 450; } // From GraphicsExample + public int getHeight() { return 180; } // From GraphicsExample + + int[] xpoints = new int[] { 0, 50, 100 }; // X coordinates of our shape + int[] ypoints = new int[] { 75, 0, 75 }; // Y coordinates of our shape + + // Here are three different line styles we will demonstrate + // They are thick lines with different cap and join styles + Stroke[] linestyles = new Stroke[] { + new BasicStroke(25.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL), + new BasicStroke(25.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER), + new BasicStroke(25.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND), + }; + + // Another line style: a 2 pixel-wide dot-dashed line + Stroke thindashed = new BasicStroke(2.0f, // line width + /* cap style */ BasicStroke.CAP_BUTT, + /* join style, miter limit */ BasicStroke.JOIN_BEVEL, 1.0f, + /* the dash pattern */ new float[] {8.0f, 3.0f, 2.0f, 3.0f}, + /* the dash phase */ 0.0f); /* on 8, off 3, on 2, off 3 */ + + // Labels to appear in the diagram, and the font to use to display them. + Font font = new Font("Helvetica", Font.BOLD, 12); + String[] capNames = new String[] {"CAP_BUTT", "CAP_SQUARE","CAP_ROUND"}; + String[] joinNames = new String[] {"JOIN_BEVEL","JOIN_MITER","JOIN_ROUND"}; + + /** This method draws the example figure */ + public void draw(Graphics2D g, Component c) { + // Use anti-aliasing to avoid "jaggies" in the lines + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + // Define the shape to draw + GeneralPath shape = new GeneralPath(); + shape.moveTo(xpoints[0], ypoints[0]); // start at point 0 + shape.lineTo(xpoints[1], ypoints[1]); // draw a line to point 1 + shape.lineTo(xpoints[2], ypoints[2]); // and then on to point 2 + + // Move the origin to the right and down, creating a margin + g.translate(20,40); + + // Now loop, drawing our shape with the three different line styles + for(int i = 0; i < linestyles.length; i++) { + g.setColor(Color.gray); // Draw a gray line + g.setStroke(linestyles[i]); // Select the line style to use + g.draw(shape); // Draw the shape + + g.setColor(Color.black); // Now use black + g.setStroke(thindashed); // And the thin dashed line + g.draw(shape); // And draw the shape again. + + // Highlight the location of the vertexes of the shape + // This accentuates the cap and join styles we're demonstrating + for(int j = 0; j < xpoints.length; j++) + g.fillRect(xpoints[j]-2, ypoints[j]-2, 5, 5); + + g.drawString(capNames[i], 5, 105); // Label the cap style + g.drawString(joinNames[i], 5, 120); // Label the join style + + g.translate(150, 0); // Move over to the right before looping again + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/Paints.java b/src/main/java/com/davidflanagan/examples/graphics/Paints.java new file mode 100644 index 0000000..ddf95fe --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/Paints.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; +import java.awt.font.*; +import java.awt.image.*; + +/** A demonstration of Java2D transformations */ +public class Paints implements GraphicsExample { + static final int WIDTH = 800, HEIGHT = 375; // Size of our example + + public String getName() { return "Paints"; } // From GraphicsExample + public int getWidth() { return WIDTH; } // From GraphicsExample + public int getHeight() { return HEIGHT; } // From GraphicsExample + + /** Draw the example */ + public void draw(Graphics2D g, Component c) { + // Paint the entire background using a GradientPaint. + // The background color varies diagonally from deep red to pale blue + g.setPaint(new GradientPaint(0, 0, new Color(150, 0, 0), + WIDTH, HEIGHT, new Color(200, 200, 255))); + g.fillRect(0, 0, WIDTH, HEIGHT); // fill the background + + // Use a different GradientPaint to draw a box. + // This one alternates between deep opaque green and transparent green. + // Note: the 4th arg to Color() constructor specifies color opacity + g.setPaint(new GradientPaint(0, 0, new Color(0, 150, 0), + 20, 20, new Color(0, 150, 0, 0), true)); + g.setStroke(new BasicStroke(15)); // use wide lines + g.drawRect(25, 25, WIDTH-50, HEIGHT-50); // draw the box + + // The glyphs of fonts can be used as Shape objects, which enables + // us to use Java2D techniques with letters Just as we would with + // any other shape. Here we get some letter shapes to draw. + Font font = new Font("Serif", Font.BOLD, 10); // a basic font + Font bigfont = // a scaled up version + font.deriveFont(AffineTransform.getScaleInstance(30.0, 30.0)); + GlyphVector gv = bigfont.createGlyphVector(g.getFontRenderContext(), + "JAV"); + Shape jshape = gv.getGlyphOutline(0); // Shape of letter J + Shape ashape = gv.getGlyphOutline(1); // Shape of letter A + Shape vshape = gv.getGlyphOutline(2); // Shape of letter V + + // We're going to outline the letters with a 5-pixel wide line + g.setStroke(new BasicStroke(5.0f)); + + // We're going to fake shadows for the letters using the + // following Paint and AffineTransform objects + Paint shadowPaint = new Color(0, 0, 0, 100); // Translucent black + AffineTransform shadowTransform = + AffineTransform.getShearInstance(-1.0, 0.0); // Shear to the right + shadowTransform.scale(1.0, 0.5); // Scale height by 1/2 + + // Move to the baseline of our first letter + g.translate(65, 270); + + // Draw the shadow of the J shape + g.setPaint(shadowPaint); + g.translate(15,20); // Compensate for the descender of the J + // transform the J into the shape of its shadow, and fill it + g.fill(shadowTransform.createTransformedShape(jshape)); + g.translate(-15,-20); // Undo the translation above + + // Now fill the J shape with a solid (and opaque) color + g.setPaint(Color.blue); // Fill with solid, opaque blue + g.fill(jshape); // Fill the shape + g.setPaint(Color.black); // Switch to solid black + g.draw(jshape); // And draw the outline of the J + + // Now draw the A shadow + g.translate(75, 0); // Move to the right + g.setPaint(shadowPaint); // Set shadow color + g.fill(shadowTransform.createTransformedShape(ashape)); // draw shadow + + // Draw the A shape using a solid transparent color + g.setPaint(new Color(0, 255, 0, 125)); // Transparent green as paint + g.fill(ashape); // Fill the shape + g.setPaint(Color.black); // Switch to solid back + g.draw(ashape); // Draw the outline + + // Move to the right and draw the shadow of the letter V + g.translate(175, 0); + g.setPaint(shadowPaint); + g.fill(shadowTransform.createTransformedShape(vshape)); + + // We're going to fill the next letter using a TexturePaint, which + // repeatedly tiles an image. The first step is to obtain the image. + // We could load it from an image file, but here we create it + // ourselves by drawing a into an off-screen image. Note that we use + // a GradientPaint to fill the off-screen image, so the fill pattern + // combines features of both Paint classes. + BufferedImage tile = // Create an image + new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB); + Graphics2D tg = tile.createGraphics(); // Get its Graphics for drawing + tg.setColor(Color.pink); + tg.fillRect(0, 0, 50, 50); // Fill tile background with pink + tg.setPaint(new GradientPaint(40, 0, Color.green, // diagonal gradient + 0, 40, Color.gray)); // green to gray + tg.fillOval(5, 5, 40, 40); // Draw a circle with this gradient + + // Use this new tile to create a TexturePaint and fill the letter V + g.setPaint(new TexturePaint(tile, new Rectangle(0, 0, 50, 50))); + g.fill(vshape); // Fill letter shape + g.setPaint(Color.black); // Switch to solid black + g.draw(vshape); // Draw outline of letter + + // Move to the right and draw the shadow of the final A + g.translate(160, 0); + g.setPaint(shadowPaint); + g.fill(shadowTransform.createTransformedShape(ashape)); + + // For the last letter, use a custom Paint class to fill with a + // complex mathematically defined pattern. The GenericPaint + // class is defined later in the chapter. + g.setPaint(new GenericPaint() { + public int computeRed(double x, double y) { return 128; } + public int computeGreen(double x, double y) { + return (int)((Math.sin(x/7) + Math.cos(y/5) + 2)/4 *255); + } + public int computeBlue(double x, double y) { + return ((int)(x*y))%256; + } + public int computeAlpha(double x, double y) { + return ((int)x%25*8+50) + ((int)y%25*8+50); + } + }); + g.fill(ashape); // Fill letter A + g.setPaint(Color.black); // Revert to solid black + g.draw(ashape); // Draw the outline of the A + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/RandomPaint.java b/src/main/java/com/davidflanagan/examples/graphics/RandomPaint.java new file mode 100644 index 0000000..0d9ff51 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/RandomPaint.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.*; + +/** + * This is a Paint implementation that fills every raster with a random + * opaque color. Using this Paint class allows you to visualize the + * painting algorithm used by your Java2D implementation. + **/ +public class RandomPaint implements Paint { + /** This is the main Paint method; all it does is return a PaintContext */ + public PaintContext createContext(ColorModel cm, + Rectangle deviceBounds, + Rectangle2D userBounds, + AffineTransform xform, + RenderingHints hints) { + return new RandomPaintContext(); + } + + /** This Paint object only uses opaque colors */ + public int getTransparency() { return OPAQUE; } + + /** + * The PaintContext class does all the work of painting + **/ + class RandomPaintContext implements PaintContext { + BufferedImage image; // An image we can draw into + Graphics2D imageGraphics; // The Graphics object to do it with + java.util.Random randomizer = // For generating random numbers + new java.util.Random(System.currentTimeMillis()); // seed value + Rectangle rect = new Rectangle(); // A scratch rectangle + + /** Return the color model used by this Paint implementation */ + public ColorModel getColorModel() { return image.getColorModel(); } + + /** + * This is the main method of PaintContext. It must return a Raster + * that contains fill data for the specified rectangle. For this + * implementation, we just fill with a random solid color each time. + * Instead of setting pixels at the Raster level, we instead + * manipulate a BufferedImage using the Graphics.fillRect() method. + * Note that we never create an image larger than we need. + **/ + public Raster getRaster(int x, int y, int w, int h) { + // Create an initial image or a larger image as needed + if ((image == null) || (image.getWidth() < w) || + (image.getHeight() < h)) { + image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + imageGraphics = image.createGraphics(); + } + + // Choose and use a random color + imageGraphics.setColor(new Color(randomizer.nextInt(256), + randomizer.nextInt(256), + randomizer.nextInt(256))); + // Fill a rectangle of the specified size with that color + imageGraphics.fillRect(0, 0, w, h); + + // Then extract the corresponding Raster from the image and return + rect.x = 0; rect.y = 0; rect.width = w; rect.height = h; + return image.getData(rect); + } + + /** Called when the PaintContext is no longer needed. */ + public void dispose() { + imageGraphics.dispose(); + image = null; + imageGraphics = null; + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/Shapes.java b/src/main/java/com/davidflanagan/examples/graphics/Shapes.java new file mode 100644 index 0000000..0b4496a --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/Shapes.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; +import java.awt.font.*; +import java.awt.image.*; + +/** A demonstration of Java2D shapes */ +public class Shapes implements GraphicsExample { + static final int WIDTH = 725, HEIGHT = 250; // Size of our example + public String getName() {return "Shapes";} // From GraphicsExample + public int getWidth() { return WIDTH; } // From GraphicsExample + public int getHeight() { return HEIGHT; } // From GraphicsExample + + Shape[] shapes = new Shape[] { + // A straight line segment + new Line2D.Float(0, 0, 100, 100), + // A quadratic bezier curve. Two end points and one control point + new QuadCurve2D.Float(0, 0, 80, 15, 100, 100), + // A cubic bezier curve. Two end points and two control points + new CubicCurve2D.Float(0, 0, 80, 15, 10, 90, 100, 100), + // A 120 degree portion of an ellipse + new Arc2D.Float(-30, 0, 100, 100, 60, -120, Arc2D.OPEN), + // A 120 degree portion of an ellipse, closed with a chord + new Arc2D.Float(-30, 0, 100, 100, 60, -120, Arc2D.CHORD), + // A 120 degree pie slice of an ellipse + new Arc2D.Float(-30, 0, 100, 100, 60, -120, Arc2D.PIE), + // An ellipse + new Ellipse2D.Float(0, 20, 100, 60), + // A rectangle + new Rectangle2D.Float(0, 20, 100, 60), + // A rectangle with rounded corners + new RoundRectangle2D.Float(0, 20, 100, 60, 15, 15), + // A triangle + new Polygon(new int[] { 0, 0, 100 }, new int[] {20, 80, 80}, 3), + // A random polygon, initialized in code below + null, + // A spiral: an instance of a custom Shape implementation + new Spiral(50, 50, 5, 0, 50, 4*Math.PI), + }; + + { // Initialize the null shape above as a Polygon with random points + Polygon p = new Polygon(); + for(int i = 0; i < 10; i++) + p.addPoint((int)(100*Math.random()), (int)(100*Math.random())); + shapes[10] = p; + } + + // These are the labels for each of the shapes + String[] labels = new String[] { + "Line2D", "QuadCurve2D", "CubicCurve2D", "Arc2D (OPEN)", + "Arc2D (CHORD)", "Arc2D (PIE)", "Ellipse2D", "Rectangle2D", + "RoundRectangle2D", "Polygon", "Polygon (random)", "Spiral" + }; + + /** Draw the example */ + public void draw(Graphics2D g, Component c) { + // Set basic drawing attributes + g.setFont(new Font("SansSerif", Font.PLAIN, 10)); // select font + g.setStroke(new BasicStroke(2.0f)); // 2 pixel lines + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // antialiasing + RenderingHints.VALUE_ANTIALIAS_ON); + g.translate(10, 10); // margins + + // Loop through each shape + for(int i = 0; i < shapes.length; i++) { + g.setColor(Color.yellow); // Set a color + g.fill(shapes[i]); // Fill the shape with it + g.setColor(Color.black); // Switch to black + g.draw(shapes[i]); // Outline the shape with it + g.drawString(labels[i], 0, 110); // Label the shape + g.translate(120, 0); // Move over for next shape + if (i % 6 == 5) g.translate(-6*120, 120); // Move down after 6 + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/Spiral.java b/src/main/java/com/davidflanagan/examples/graphics/Spiral.java new file mode 100644 index 0000000..3046c4b --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/Spiral.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; + +/** This Shape implementation represents a spiral curve */ +public class Spiral implements Shape { + double centerX, centerY; // The center of the spiral + double startRadius, startAngle; // The spiral starting point + double endRadius, endAngle; // The spiral ending point + double outerRadius; // the bigger of the two radii + int angleDirection; // 1 if angle increases, -1 otherwise + + /** + * The constructor. It takes arguments for the center of the shape, the + * start point, and the end point. The start and end points are specified + * in terms of angle and radius. The spiral curve is formed by varying + * the angle and radius smoothly between the two end points. + **/ + public Spiral(double centerX, double centerY, + double startRadius, double startAngle, + double endRadius, double endAngle) + { + // Save the parameters that describe the spiral + this.centerX = centerX; this.centerY = centerY; + this.startRadius = startRadius; this.startAngle = startAngle; + this.endRadius = endRadius; this.endAngle = endAngle; + + // figure out the maximum radius, and the spiral direction + this.outerRadius = Math.max(startRadius, endRadius); + if (startAngle < endAngle) angleDirection = 1; + else angleDirection = -1; + + if ((startRadius < 0) || (endRadius < 0)) + throw new IllegalArgumentException("Spiral radii must be >= 0"); + } + + /** + * The bounding box of a Spiral is the same as the bounding box of a + * circle with the same center and the maximum radius + **/ + public Rectangle getBounds() { + return new Rectangle((int)(centerX-outerRadius), + (int)(centerY-outerRadius), + (int)(outerRadius*2), (int)(outerRadius*2)); + } + + /** Same as getBounds(), but with floating-point coordinates */ + public Rectangle2D getBounds2D() { + return new Rectangle2D.Double(centerX-outerRadius, centerY-outerRadius, + outerRadius*2, outerRadius*2); + } + + /** + * A spiral is an open curve, not a not a closed area; it does not have an + * inside and an outsize, so the contains() methods always return false. + **/ + public boolean contains(double x, double y) { return false; } + public boolean contains(Point2D p) { return false; } + public boolean contains(Rectangle2D r) { return false; } + public boolean contains(double x, double y, double w, double h) { + return false; + } + + /** + * This method is allowed to approximate if it would be too computationally + * intensive to determine an exact answer. Therefore, we check whether + * the rectangle intersects a circle of the outer radius. This is a good + * guess for a tight spiral, but less good for a "loose" spiral. + **/ + public boolean intersects(double x, double y, double w, double h) { + Shape approx = new Ellipse2D.Double(centerX-outerRadius, + centerY-outerRadius, + outerRadius*2, outerRadius*2); + return approx.intersects(x, y, w, h); + } + + /** This version of intersects() just calls the one above */ + public boolean intersects(Rectangle2D r) { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * This method is the heart of all Shape implementations. It returns a + * PathIterator that describes the shape in terms of the line and curve + * segments that comprise it. Our iterator implementation approximates + * the shape of the spiral using line segments only. We pass in a + * "flatness" argument that tells it how good the approximation must be. + * (smaller numbers mean a better approximation). + */ + public PathIterator getPathIterator(AffineTransform at) { + return new SpiralIterator(at, outerRadius/500.0); + } + + /** + * Return a PathIterator that describes the shape in terms of line + * segments only, with an approximation quality specified by flatness. + **/ + public PathIterator getPathIterator(AffineTransform at, double flatness) { + return new SpiralIterator(at, flatness); + } + + /** + * This inner class is the PathIterator for our Spiral shape. For + * simplicity, it does not describe the spiral path in terms of Bezier + * curve segments, but simply approximates it with line segments. The + * flatness property specifies how far the approximation is allowed to + * deviate from the true curve. + **/ + class SpiralIterator implements PathIterator { + AffineTransform transform; // How to transform generated coordinates + double flatness; // How close an approximation + double angle = startAngle; // Current angle + double radius = startRadius; // Current radius + boolean done = false; // Are we done yet? + + /** A simple constructor. Just store the parameters into fields */ + public SpiralIterator(AffineTransform transform, double flatness) { + this.transform = transform; + this.flatness = flatness; + } + + /** + * All PathIterators have a "winding rule" that helps to specify what + * is the inside of a area and what is the outside. If you fill a + * spiral (which you're not supposed to do) the winding rule returned + * here yields better results than the alternative, WIND_EVEN_ODD + **/ + public int getWindingRule() { return WIND_NON_ZERO; } + + /** Returns true if the entire path has been iterated */ + public boolean isDone() { return done; } + + /** + * Store the coordinates of the current segment of the path into the + * specified array, and return the type of the segment. Use + * trigonometry to compute the coordinates based on the current angle + * and radius. If this was the first point, return a MOVETO segment, + * otherwise return a LINETO segment. Also, check to see if we're done. + **/ + public int currentSegment(float[] coords) { + // given the radius and the angle, compute the point coords + coords[0] = (float)(centerX + radius*Math.cos(angle)); + coords[1] = (float)(centerY - radius*Math.sin(angle)); + + // If a transform was specified, use it on the coordinates + if (transform != null) transform.transform(coords, 0, coords, 0,1); + + // If we've reached the end of the spiral remember that fact + if (angle == endAngle) done = true; + + // If this is the first point in the spiral then move to it + if (angle == startAngle) return SEG_MOVETO; + + // Otherwise draw a line from the previous point to this one + return SEG_LINETO; + } + + /** This method is the same as above, except using double values */ + public int currentSegment(double[] coords) { + coords[0] = centerX + radius*Math.cos(angle); + coords[1] = centerY - radius*Math.sin(angle); + if (transform != null) transform.transform(coords, 0, coords, 0,1); + if (angle == endAngle) done = true; + if (angle == startAngle) return SEG_MOVETO; + else return SEG_LINETO; + } + + /** + * Move on to the next segment of the path. Compute the angle and + * radius values for the next point in the spiral. + **/ + public void next() { + if (done) return; + + // First, figure out how much to increment the angle. This + // depends on the required flatness, and also upon the current + // radius. When drawing a circle (which we'll use as our + // approximation) of radius r, we can maintain a flatness f by + // using angular increments given by this formula: + // a = acos(2*(f/r)*(f/r) - 4*(f/r) + 1) + // Use this formula to figure out how much we can increment the + // angle for the next segment. Note that the formula does not + // work well for very small radii, so we special case those. + double x = flatness/radius; + if (Double.isNaN(x) || (x > .1)) + angle += Math.PI/4*angleDirection; + else { + double y = 2*x*x - 4*x + 1; + angle += Math.acos(y)*angleDirection; + } + + // Check whether we've gone past the end of the spiral + if ((angle-endAngle)*angleDirection > 0) angle = endAngle; + + // Now that we know the new angle, we can use interpolation to + // figure out what the corresponding radius is. + double fractionComplete = (angle-startAngle)/(endAngle-startAngle); + radius = startRadius + (endRadius-startRadius)*fractionComplete; + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/Stroking.java b/src/main/java/com/davidflanagan/examples/graphics/Stroking.java new file mode 100644 index 0000000..616afd5 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/Stroking.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; + +/** A demonstration of how Stroke objects work */ +public class Stroking implements GraphicsExample { + static final int WIDTH = 725, HEIGHT = 250; // Size of our example + public String getName() {return "Stroking";} // From GraphicsExample + public int getWidth() { return WIDTH; } // From GraphicsExample + public int getHeight() { return HEIGHT; } // From GraphicsExample + + /** Draw the example */ + public void draw(Graphics2D g, Component c) { + // Create the shape we'll work with. See convenience method below. + Shape pentagon = createRegularPolygon(5, 75); + + // Set up basic drawing attributes + g.setColor(Color.black); // Draw in black + g.setStroke(new BasicStroke(1.0f)); // Use thin lines + g.setFont(new Font("SansSerif", Font.PLAIN, 12)); // Basic small font + + g.translate(100, 100); // Move to position + g.draw(pentagon); // Outline the shape + g.drawString("The shape", -30, 90); // Draw the caption + + g.translate(175, 0); // Move over + g.fill(pentagon); // Fill the shape + g.drawString("The filled shape", -50, 90); // Another caption + + // Now use a Stroke object to create a "stroked shape" for our shape + BasicStroke wideline = new BasicStroke(10.0f); + Shape outline = wideline.createStrokedShape(pentagon); + + g.translate(175, 0); // Move over + g.draw(outline); // Draw the stroked shape + g.drawString("A Stroke creates",-50,90); // Draw the caption + g.drawString("a new shape", -35, 105); + + g.translate(175,0); // Move over + g.fill(outline); // Fill the stroked shape + g.drawString("Filling the new shape",-65,90); // Draw the caption + g.drawString("outlines the old one",-65,105); + } + + // A convenience method to define a regular polygon. + // Returns a shape that represents a regular polygon with the specified + // radius and number of sides, and centered at the origin. + public Shape createRegularPolygon(int numsides, int radius) { + Polygon p = new Polygon(); + double angle = 2 * Math.PI / numsides; // Angle between vertices + for(int i = 0; i < numsides; i++) // Compute location of each vertex + p.addPoint((int)(radius * Math.sin(angle*i)), + (int)(radius * -Math.cos(angle*i))); + return p; + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/Transforms.java b/src/main/java/com/davidflanagan/examples/graphics/Transforms.java new file mode 100644 index 0000000..950da35 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/graphics/Transforms.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.graphics; +import java.awt.*; +import java.awt.geom.*; + +/** A demonstration of Java2D transformations */ +public class Transforms implements GraphicsExample { + public String getName() { return "Transforms"; } // From GraphicsExample + public int getWidth() { return 750; } // From GraphicsExample + public int getHeight() { return 250; } // From GraphicsExample + + Shape shape; // The shape to draw + AffineTransform[] transforms; // The ways to transform it + String[] transformLabels; // Labels for each transform + + /** + * This constructor sets up the Shape and AffineTransform objects we need + **/ + public Transforms() { + GeneralPath path = new GeneralPath(); // Create a shape to draw + path.append(new Line2D.Float(0.0f, 0.0f, 0.0f, 100.0f), false); + path.append(new Line2D.Float(-10.0f, 50.0f, 10.0f, 50.0f), false); + path.append(new Polygon(new int[] { -5, 0, 5 }, + new int[] { 5, 0, 5 }, 3), false); + this.shape = path; // Remember this shape + + // Set up some transforms to alter the shape + this.transforms = new AffineTransform[6]; + // 1) the identity transform + transforms[0] = new AffineTransform(); + // 2) A scale tranform: 3/4 size + transforms[1] = AffineTransform.getScaleInstance(0.75, 0.75); + // 3) A shearing transform + transforms[2] = AffineTransform.getShearInstance(-0.4, 0.0); + // 4) A 30 degree clockwise rotation about the origin of the shape + transforms[3] = AffineTransform.getRotateInstance(Math.PI*2/12); + // 5) A 180 degree rotation about the midpoint of the shape + transforms[4] = AffineTransform.getRotateInstance(Math.PI, 0.0, 50.0); + // 6) A combination transform + transforms[5] = AffineTransform.getScaleInstance(0.5, 1.5); + transforms[5].shear(0.0, 0.4); + transforms[5].rotate(Math.PI/2, 0.0, 50.0); // 90 degrees + + // Define names for the transforms + transformLabels = new String[] { + "identity", "scale", "shear", "rotate", "rotate", "combo" + }; + } + + /** Draw the defined shape and label, using each transform */ + public void draw(Graphics2D g, Component c) { + // Define basic drawing attributes + g.setColor(Color.black); // black + g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_SQUARE, // 2-pixel + BasicStroke.JOIN_BEVEL)); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // antialias + RenderingHints.VALUE_ANTIALIAS_ON); + + // Now draw the shape once using each of the transforms we've defined + for(int i = 0; i < transforms.length; i++) { + AffineTransform save = g.getTransform(); // save current state + g.translate(i*125 + 50, 50); // move origin + g.transform(transforms[i]); // apply transform + g.draw(shape); // draw shape + g.drawString(transformLabels[i], -25, 125); // draw label + g.drawRect(-40, -10, 80, 150); // draw box + g.setTransform(save); // restore transform + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/graphics/background.gif b/src/main/java/com/davidflanagan/examples/graphics/background.gif new file mode 100644 index 0000000..34072cf Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/graphics/background.gif differ diff --git a/src/main/java/com/davidflanagan/examples/graphics/cover.gif b/src/main/java/com/davidflanagan/examples/graphics/cover.gif new file mode 100644 index 0000000..d4c58a8 Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/graphics/cover.gif differ diff --git a/src/main/java/com/davidflanagan/examples/graphics/tiger.gif b/src/main/java/com/davidflanagan/examples/graphics/tiger.gif new file mode 100644 index 0000000..b455f3e Binary files /dev/null and b/src/main/java/com/davidflanagan/examples/graphics/tiger.gif differ diff --git a/src/main/java/com/davidflanagan/examples/gui/ActionParser.java b/src/main/java/com/davidflanagan/examples/gui/ActionParser.java new file mode 100644 index 0000000..ebdb6bd --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ActionParser.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import com.davidflanagan.examples.reflect.*; +import java.awt.event.*; +import javax.swing.*; +import java.util.*; + +/** + * This class parses an Action object from a GUIResourceBundle. + * The specified key is used to look up the Command string for the action. + * The key is also used as a prefix for other resource names that specify + * other attributes (such as the label and icon) associated with the Action. + * An action named "zoomOut" might be specified like this: + * + * zoomOut: zoom(0.5); + * zoomOut.label: Zoom Out + * zoomOut.description: Zoom out by a factor of 2 + * + * Because Action objects are often reused by an application (for example + * in a toolbar and a menu system, this ResourceParser caches the Action + * objects it returns. By sharing Action objects, you can disable and enable + * an action and that change will affect the entire GUI. + **/ +public class ActionParser implements ResourceParser { + static final Class[] supportedTypes = new Class[] { Action.class }; + public Class[] getResourceTypes() { return supportedTypes; } + + HashMap bundleToCacheMap = new HashMap(); + + public Object parse(GUIResourceBundle bundle, String key, Class type) + throws MissingResourceException + { + // Look up the Action cache associated with this bundle + HashMap cache = (HashMap) bundleToCacheMap.get(bundle); + if (cache == null) { // If there isn't one, create one and save it + cache = new HashMap(); + bundleToCacheMap.put(bundle, cache); + } + // Now look up the Action associated with the key in the cache. + Action action = (Action) cache.get(key); + // If we found a cached action, return it. + if (action != null) return action; + + // If there was no cached action create one. The command is + // the only required resource. It will throw an exception if + // missing or malformed. + Command command = (Command) bundle.getResource(key, Command.class); + + // The remaining calls all supply default values, so they will not + // throw exceptions, even if ResourceParsers haven't been registered + // for types like Icon and KeyStroke + String label = bundle.getString(key + ".label", null); + Icon icon = (Icon) bundle.getResource(key + ".icon", Icon.class, null); + String tooltip = bundle.getString(key + ".description", null); + KeyStroke accelerator = + (KeyStroke) bundle.getResource(key + ".accelerator", + KeyStroke.class, null); + int mnemonic = bundle.getInt(key + ".mnemonic", KeyEvent.VK_UNDEFINED); + boolean enabled = bundle.getBoolean(key + ".enabled", true); + + // Create a CommandAction object with these values + action = new CommandAction(command, label, icon, tooltip, + accelerator, mnemonic, enabled); + + // Save it in the cache, then return it + cache.put(key, action); + return action; + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/AppletMenuBar.java b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBar.java new file mode 100644 index 0000000..585bde9 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBar.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.awt.event.*; +import java.util.Vector; + +public class AppletMenuBar extends Panel { + // Menubar contents + Vector labels = new Vector(); + Vector menus = new Vector(); + + // Properties + Insets margins = new Insets(3, 10, 3, 10); // top, left, bottom, right + int spacing = 10; // Space between menu labels + Color highlightColor; // Rollover color for labels + + // internal stuff + boolean remeasure = true; // Whether the labels need to be remeasured + int[] widths; // The width of each label + int[] startPositions; // Where each label starts + int ascent, descent; // Font metrics + Dimension prefsize = new Dimension(); // How big do we want to be? + int highlightedItem = -1; // Which item is the mouse over? + + /** + * Create a new component that simulates a menubar by displaying + * the specified labels. Whenever the user clicks the specified label, + * popup up the PopupMenu specified in the menus array. + * Elements of the menus arra may be a static PopupMenu object, or + * a PopupMenuFactory object for dynamically creating menus. + * Perhaps we'll also provide some other kind of constructor or factory + * method that reads popup menus out of a config file. + */ + public AppletMenuBar() { + // We'd like these kinds of events to be delivered + enableEvents(AWTEvent.MOUSE_EVENT_MASK | + AWTEvent.MOUSE_MOTION_EVENT_MASK); + } + + /** Add a popup menu to the menubar */ + public void addMenu(String label, PopupMenu menu) { + insertMenu(label, menu, -1); + } + + /** Insert a popup menu into the menubar */ + public void insertMenu(String label, PopupMenu menu, int index) { + if (index < 0) index += labels.size()+1; // Position to put it at + this.add(menu); // Popup belongs to us + labels.insertElementAt(label, index); // Remember the label + menus.insertElementAt(menu, index); // Remember the menu + remeasure = true; // Remeasure everything + invalidate(); // Container must relayout + } + + /** Property accessor methods for margins property */ + public Insets getMargins() { return (Insets) margins.clone(); } + public void setMargins(Insets margins) { + this.margins = margins; + remeasure = true; + invalidate(); + } + + /** Property accessor methods for spacing property */ + public int getSpacing() { return spacing; } + public void setSpacing(int spacing) { + if (this.spacing != spacing) { + this.spacing = spacing; + remeasure = true; + invalidate(); + } + } + + /** Accessor methods for highlightColor property */ + public Color getHighlightColor() { + if (highlightColor == null) return getForeground(); + else return highlightColor; + } + public void setHighlightColor(Color c) { + if (highlightColor != c) { + highlightColor = c; + repaint(); + } + } + + /** We override the setFont() method so we can remeasure */ + public void setFont(Font f) { + super.setFont(f); + remeasure = true; + invalidate(); + } + + /** Override these color property setter method so we can repaint */ + public void setForeground(Color c) { + super.setForeground(c); + repaint(); + } + public void setBackground(Color c) { + super.setBackground(c); + repaint(); + } + + /** + * This method is called to draw tell the component to redraw itself. + * If we were implementing a Swing component, we'd override + * paintComponent() instead + **/ + public void paint(Graphics g) { + if (remeasure) measure(); // Remeasure everything first, if needed + + // Figure out Y coordinate to draw at + Dimension size = getSize(); + int baseline = size.height - margins.bottom - descent; + // Set the font to draw with + g.setFont(getFont()); + // Loop through the labels + int nummenus = labels.size(); + for(int i = 0; i < nummenus; i++) { + // Set the drawing color. Highlight the current item + if ((i == highlightedItem) && (highlightColor != null)) + g.setColor(getHighlightColor()); + else + g.setColor(getForeground()); + + // Draw the menu label at the position computed in measure() + g.drawString((String)labels.elementAt(i), + startPositions[i], baseline); + } + + // Now draw a groove at the bottom of the menubar. + Color bg = getBackground(); + g.setColor(bg.darker()); + g.drawLine(0, size.height-2, size.width, size.height-2); + g.setColor(bg.brighter()); + g.drawLine(0, size.height-1, size.width, size.height-1); + } + + /** Called when a mouse event happens over the menubar */ + protected void processMouseEvent(MouseEvent e) { + int type = e.getID(); // What type of event? + int item = findItemAt(e.getX()); // Over which menu label? + + if (type == MouseEvent.MOUSE_PRESSED) { + // If it was a mouse down event, then pop up the menu + if (item == -1) return; + Dimension size = getSize(); + PopupMenu pm = (PopupMenu) menus.elementAt(item); + if (pm != null) pm.show(this, startPositions[item]-3, size.height); + + } + else if (type == MouseEvent.MOUSE_EXITED) { + // If the mouse left the menubar, then unhighlight + if (highlightedItem != -1) { + highlightedItem = -1; + if (highlightColor != null) repaint(); + } + } + else if ((type == MouseEvent.MOUSE_MOVED) || + (type == MouseEvent.MOUSE_ENTERED)) { + // If the mouse moved, change the highlighted item, if necessary + if (item != highlightedItem) { + highlightedItem = item; + if (highlightColor != null) repaint(); + } + } + } + + /** This method is called when the mouse moves */ + protected void processMouseMotionEvent(MouseEvent e) { + processMouseEvent(e); + } + + /** This utility method converts an X coordinate to a menu label index */ + protected int findItemAt(int x) { + // This could be a more efficient search... + int nummenus = labels.size(); + int halfspace = spacing/2-1; + int i; + for(i = nummenus-1; i >= 0; i--) { + if ((x >= startPositions[i]-halfspace) && + (x <= startPositions[i]+widths[i]+halfspace)) break; + } + return i; + } + + + /** + * Measure the menu labels, and figure out their positions, so we + * can determine when a click happens, and so we can redraw efficiently. + **/ + protected void measure() { + // Get information about the font + FontMetrics fm = this.getFontMetrics(getFont()); + // Remember the basic font size + ascent = fm.getAscent(); + descent = fm.getDescent(); + // Create arrays to hold the measurements and positions + int nummenus = labels.size(); + widths = new int[nummenus]; + startPositions = new int[nummenus]; + + // Measure the label strings and + // figure out the starting position of each label + int pos = margins.left; + for(int i = 0; i < nummenus; i++) { + startPositions[i] = pos; + String label = (String)labels.elementAt(i); + widths[i] = fm.stringWidth(label); + pos += widths[i] + spacing; + } + + // Compute our preferred size from this data + prefsize.width = pos - spacing + margins.right; + prefsize.height = ascent + descent + margins.top + margins.bottom; + + // We've don't need to be remeasured anymore. + remeasure = false; + } + + /** + * These methods tell the container how big the menubar wants to be. + * + **/ + public Dimension getMinimumSize() { return getPreferredSize(); } + public Dimension getPreferredSize() { + if (remeasure) measure(); + return prefsize; + } + /** @deprecated Here for compatibility with Java 1.0 */ + public Dimension minimumSize() { return getPreferredSize(); } + /** @deprecated Here for compatibility with Java 1.0 */ + public Dimension preferredSize() { return getPreferredSize(); } + + /** + * This method is called when the underlying AWT component is created. + * We can't measure ourselves (no font metrics) until this is called. + **/ + public void addNotify() { + super.addNotify(); + measure(); + } + + /** This method tells the container not to give us keyboard focus */ + public boolean isFocusTraversable() { return false; } +} + diff --git a/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.html b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.html new file mode 100644 index 0000000..adfa9cb --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.html @@ -0,0 +1,4 @@ + + diff --git a/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.java b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.java new file mode 100644 index 0000000..35067fb --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/AppletMenuBarDemo.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.applet.*; + +public class AppletMenuBarDemo extends Applet { + public void init() { + AppletMenuBar menubar = new AppletMenuBar(); + menubar.setForeground(Color.black); + menubar.setHighlightColor(Color.red); + menubar.setFont(new Font("helvetica", Font.BOLD, 12)); + this.setLayout(new BorderLayout()); + this.add(menubar, BorderLayout.NORTH); + + PopupMenu file = new PopupMenu(); + file.add("New..."); file.add("Open..."); file.add("Save As..."); + PopupMenu edit = new PopupMenu(); + edit.add("Cut"); edit.add("Copy"); edit.add("Paste"); + + menubar.addMenu("File", file); + menubar.addMenu("Edit", edit); + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/BorderLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/BorderLayoutPane.java new file mode 100644 index 0000000..70e889a --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/BorderLayoutPane.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import javax.swing.*; + +public class BorderLayoutPane extends JPanel { + String[] borders = {"North", "East", "South", "West", "Center"}; + public BorderLayoutPane() { + // Use a BorderLayout with 10-pixel margins between components + this.setLayout(new BorderLayout(10, 10)); + for(int i = 0; i < 5; i++) { // Add children to the pane + this.add(new JButton(borders[i]), // Add this component + borders[i]); // Using this constraint + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/BoxLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/BoxLayoutPane.java new file mode 100644 index 0000000..54caac5 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/BoxLayoutPane.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import javax.swing.*; +import javax.swing.border.*; + +public class BoxLayoutPane extends JPanel { + public BoxLayoutPane() { + // Use a BorderLayout layout manager to arrange various Box components + this.setLayout(new BorderLayout()); + + // Give the entire panel a margin by adding an empty border + // We could also do this by overriding getInsets() + this.setBorder(new EmptyBorder(10,10,10,10)); + + // Add a plain row of buttons along the top of the pane + Box row = Box.createHorizontalBox(); + for(int i = 0; i < 4; i++) { + JButton b = new JButton("B" + i); + b.setFont(new Font("serif", Font.BOLD, 12+i*2)); + row.add(b); + } + this.add(row, BorderLayout.NORTH); + + // Add a plain column of buttons along the right edge + // Use BoxLayout with a different kind of Swing container + // Give the column a border: can't do this with the Box class + JPanel col = new JPanel(); + col.setLayout(new BoxLayout(col, BoxLayout.Y_AXIS)); + col.setBorder(new TitledBorder(new EtchedBorder(), "Column")); + for(int i = 0; i < 4; i++) { + JButton b = new JButton("Button " + i); + b.setFont(new Font("sanserif", Font.BOLD, 10+i*2)); + col.add(b); + } + this.add(col, BorderLayout.EAST); // Add column to right of panel + + // Add a button box along the bottom of the panel. + // Use "Glue" to space the buttons evenly + Box buttonbox = Box.createHorizontalBox(); + buttonbox.add(Box.createHorizontalGlue()); // stretchy space + buttonbox.add(new JButton("Okay")); + buttonbox.add(Box.createHorizontalGlue()); // stretchy space + buttonbox.add(new JButton("Cancel")); + buttonbox.add(Box.createHorizontalGlue()); // stretchy space + buttonbox.add(new JButton("Help")); + buttonbox.add(Box.createHorizontalGlue()); // stretchy space + this.add(buttonbox, BorderLayout.SOUTH); + + // Create a component to display in the center of the panel + JTextArea textarea = new JTextArea(); + textarea.setText("This component has 12-pixel margins on left and top"+ + " and has 72-pixel margins on right and bottom."); + textarea.setLineWrap(true); + textarea.setWrapStyleWord(true); + + // Use Box objects to give the JTextArea an unusual spacing + // First, create a column with 3 kids. The first and last kids + // are rigid spaces. The middle kid is the text area + Box fixedcol = Box.createVerticalBox(); + fixedcol.add(Box.createVerticalStrut(12)); // 12 rigid pixels + fixedcol.add(textarea); // Component fills in the rest + fixedcol.add(Box.createVerticalStrut(72)); // 72 rigid pixels + + // Now create a row. Give it rigid spaces on the left and right, + // and put the column from above in the middle. + Box fixedrow = Box.createHorizontalBox(); + fixedrow.add(Box.createHorizontalStrut(12)); + fixedrow.add(fixedcol); + fixedrow.add(Box.createHorizontalStrut(72)); + + // Now add the JTextArea in the column in the row to the panel + this.add(fixedrow, BorderLayout.CENTER); + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ColumnLayout.java b/src/main/java/com/davidflanagan/examples/gui/ColumnLayout.java new file mode 100644 index 0000000..33bab07 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ColumnLayout.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; + +/** + * This LayoutManager arranges the components into a column. + * Components are always given their preferred size. + * + * When you create a ColumnLayout, you may specify four values: + * margin_height -- how much space to leave on top and bottom + * margin_width -- how much space to leave on left and right + * spacing -- how much vertical space to leave between items + * alignment -- the horizontal position of the components: + * ColumnLayout.LEFT -- left-justify the components + * ColumnLayout.CENTER -- horizontally center the components + * ColumnLayout.RIGHT -- right-justify the components + * + * You never call the methods of a ColumnLayout object. Just create one + * and make it the layout manager for your container by passing it to + * the addLayout() method of the Container object. + */ +public class ColumnLayout implements LayoutManager2 { + protected int margin_height; + protected int margin_width; + protected int spacing; + protected int alignment; + + // Constants for the alignment argument to the constructor. + public static final int LEFT = 0; + public static final int CENTER = 1; + public static final int RIGHT = 2; + + /** The constructor. See comment above for meanings of these arguments */ + public ColumnLayout(int margin_height, int margin_width, + int spacing, int alignment) { + this.margin_height = margin_height; + this.margin_width = margin_width; + this.spacing = spacing; + this.alignment = alignment; + } + + /** + * A default constructor that creates a ColumnLayout using 5-pixel + * margin width and height, 5-pixel spacing, and left alignment + **/ + public ColumnLayout() { this(5, 5, 5, LEFT); } + + /** + * The method that actually performs the layout. + * Called by the Container + **/ + public void layoutContainer(Container parent) { + Insets insets = parent.getInsets(); + Dimension parent_size = parent.getSize(); + Component kid; + int nkids = parent.getComponentCount(); + int x0 = insets.left + margin_width; // The base X position + int x; + int y = insets.top + margin_height; // Start at the top of the column + + for(int i = 0; i < nkids; i++) { // Loop through the kids + kid = parent.getComponent(i); // Get the kid + if (!kid.isVisible()) continue; // Skip hidden ones + Dimension pref = kid.getPreferredSize(); // How big is it? + switch(alignment) { // Compute X coordinate + default: + case LEFT: x = x0; break; + case CENTER: x = (parent_size.width - pref.width)/2; break; + case RIGHT: + x = parent_size.width-insets.right-margin_width-pref.width; + break; + } + // Set the size and position of this kid + kid.setBounds(x, y, pref.width, pref.height); + y += pref.height + spacing; // Get Y position of the next one + } + } + + /** The Container calls this to find out how big the layout should to be */ + public Dimension preferredLayoutSize(Container parent) { + return layoutSize(parent, 1); + } + /** The Container calls this to find out how big the layout must be */ + public Dimension minimumLayoutSize(Container parent) { + return layoutSize(parent, 2); + } + /** The Container calls this to find out how big the layout can be */ + public Dimension maximumLayoutSize(Container parent) { + return layoutSize(parent, 3); + } + + // Compute min, max, or preferred size of all the visible children + protected Dimension layoutSize(Container parent, int sizetype) { + int nkids = parent.getComponentCount(); + Dimension size = new Dimension(0,0); + Insets insets = parent.getInsets(); + int num_visible_kids = 0; + + // Compute maximum width and total height of all visible kids + for(int i = 0; i < nkids; i++) { + Component kid = parent.getComponent(i); + Dimension d; + if (!kid.isVisible()) continue; + num_visible_kids++; + if (sizetype == 1) d = kid.getPreferredSize(); + else if (sizetype == 2) d = kid.getMinimumSize(); + else d = kid.getMaximumSize(); + if (d.width > size.width) size.width = d.width; + size.height += d.height; + } + + // Now add in margins and stuff + size.width += insets.left + insets.right + 2*margin_width; + size.height += insets.top + insets.bottom + 2*margin_height; + if (num_visible_kids > 1) + size.height += (num_visible_kids - 1) * spacing; + return size; + } + + // Other LayoutManager(2) methods that are unused by this class + public void addLayoutComponent(String constraint, Component comp) {} + public void addLayoutComponent(Component comp, Object constraint) {} + public void removeLayoutComponent(Component comp) {} + public void invalidateLayout(Container parent) {} + public float getLayoutAlignmentX(Container parent) { return 0.5f; } + public float getLayoutAlignmentY(Container parent) { return 0.5f; } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ColumnLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/ColumnLayoutPane.java new file mode 100644 index 0000000..21f3636 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ColumnLayoutPane.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import javax.swing.*; + +public class ColumnLayoutPane extends JPanel { + public ColumnLayoutPane() { + // Get rid of the default layout manager. + // We'll arrange the components ourselves. + this.setLayout(new ColumnLayout(5, 5, 10, ColumnLayout.RIGHT)); + + // Create some buttons and set their sizes and positions explicitly + for(int i = 0; i < 6; i++) { + int pointsize = 8 + i*2; + JButton b = new JButton("Point size " + pointsize); + b.setFont(new Font("helvetica", Font.BOLD, pointsize)); + this.add(b); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/CommandAction.java b/src/main/java/com/davidflanagan/examples/gui/CommandAction.java new file mode 100644 index 0000000..a70bfda --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/CommandAction.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import com.davidflanagan.examples.reflect.*; +import javax.swing.*; +import java.awt.event.*; + +public class CommandAction extends AbstractAction { + Command command; // The command to execute in response to an ActionEvent + + /** + * Create an Action object that has the various specified attributes, + * and invokes the specified Command object in response to ActionEvents + **/ + public CommandAction(Command command, String label, + Icon icon, String tooltip, + KeyStroke accelerator, int mnemonic, + boolean enabled) + { + this.command = command; // Remember the command to invoke + + // Set the various action attributes with putValue() + if (label != null) putValue(NAME, label); + if (icon != null) putValue(SMALL_ICON, icon); + if (tooltip != null) putValue(SHORT_DESCRIPTION, tooltip); + if (accelerator != null) putValue(ACCELERATOR_KEY, accelerator); + if (mnemonic != KeyEvent.VK_UNDEFINED) + putValue(MNEMONIC_KEY, new Integer(mnemonic)); + + // Tell the action whether it is currently enabled or not + setEnabled(enabled); + } + + /** + * This method implements ActionListener, which is a super-interface of + * Action. When a component generates an ActionEvent, it is passed to + * this method. This method simply passes it on to the Command object + * which is also an ActionListener object + **/ + public void actionPerformed(ActionEvent e) { command.actionPerformed(e); } + + // These constants are defined by Action in Java 1.3. + // For compatibility with Java 1.2, we re-define them here. + public static final String ACCELERATOR_KEY = "AcceleratorKey"; + public static final String MNEMONIC_KEY = "MnemonicKey"; +} diff --git a/src/main/java/com/davidflanagan/examples/gui/CommandParser.java b/src/main/java/com/davidflanagan/examples/gui/CommandParser.java new file mode 100644 index 0000000..62cbfdb --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/CommandParser.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import com.davidflanagan.examples.reflect.Command; + +/** + * This class parses a Command object from a GUIResourceBundle. It uses + * the Command.parse() method to perform all the actual parsing work. + **/ +public class CommandParser implements ResourceParser { + static final Class[] supportedTypes = new Class[] { Command.class }; + public Class[] getResourceTypes() { return supportedTypes;} + + public Object parse(GUIResourceBundle bundle, String key, Class type) + throws java.util.MissingResourceException, java.io.IOException + { + String value = bundle.getString(key); // look up the command text + return Command.parse(bundle.getRoot(), value); // parse it! + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ComponentTree.java b/src/main/java/com/davidflanagan/examples/gui/ComponentTree.java new file mode 100644 index 0000000..244c60d --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ComponentTree.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.tree.*; + +/** + * This class is a JTree subclass that displays the tree of AWT or Swing + * component that make up a GUI. + **/ +public class ComponentTree extends JTree { + /** + * All this constructor method has to do is set the TreeModel and + * TreeCellRenderer objects for the tree. It is these classes (defined + * below) that do all the real work. + **/ + public ComponentTree(Component c) { + super(new ComponentTreeModel(c)); + setCellRenderer(new ComponentCellRenderer(getCellRenderer())); + } + + /** + * The TreeModel class puts hierarchical data in a form that the JTree + * can display. This implementation interprets the containment hierarchy + * of a Component for display by the ComponentTree class. Note that any + * kind of Object can be a node in the tree, as long as the TreeModel knows + * how to handle it. + **/ + static class ComponentTreeModel implements TreeModel { + Component root; // The root object of the tree + + // Constructor: just remember the root object + public ComponentTreeModel(Component root) { this.root = root; } + + // Return the root of the tree + public Object getRoot() { return root; } + + // Is this node a leaf? (Leaf nodes are displayed differently by JTree) + // Any node that isn't a container is a leaf, since they cannot have + // children. We also define containers with no children as leaves. + public boolean isLeaf(Object node) { + if (!(node instanceof Container)) return true; + Container c = (Container) node; + return c.getComponentCount() == 0; + } + + // How many children does this node have? + public int getChildCount(Object node) { + if (node instanceof Container) { + Container c = (Container) node; + return c.getComponentCount(); + } + return 0; + } + + // Return the specified child of a parent node. + public Object getChild(Object parent, int index) { + if (parent instanceof Container) { + Container c = (Container) parent; + return c.getComponent(index); + } + return null; + } + + // Return the index of the child node in the parent node + public int getIndexOfChild(Object parent, Object child) { + if (!(parent instanceof Container)) return -1; + Container c = (Container) parent; + Component[] children = c.getComponents(); + if (children == null) return -1; + for(int i = 0; i < children.length; i++) { + if (children[i] == child) return i; + } + return -1; + } + + // This method is only required for editable trees, so it is not + // implemented here. + public void valueForPathChanged(TreePath path, Object newvalue) {} + + // This TreeModel never fires any events (since it is not editable) + // so event listener registration methods are left unimplemented + public void addTreeModelListener(TreeModelListener l) {} + public void removeTreeModelListener(TreeModelListener l) {} + } + + + /** + * A TreeCellRenderer displays each node of a tree. The default renderer + * displays arbitrary Object nodes by calling their toString() method. + * The Component.toString() method returns long strings with extraneous + * information. Therefore, we use this "wrapper" implementation of + * TreeCellRenderer to convert nodes from Component objects to useful + * String values before passing those String values on to the default + * renderer. + **/ + static class ComponentCellRenderer implements TreeCellRenderer { + TreeCellRenderer renderer; // The renderer we are a wrapper for + // Constructor: just remember the renderer + public ComponentCellRenderer(TreeCellRenderer renderer) { + this.renderer = renderer; + } + + // This is the only TreeCellRenderer method. + // Compute the string to display, and pass it to the wrapped renderer + public Component getTreeCellRendererComponent(JTree tree, Object value, + boolean selected, + boolean expanded, + boolean leaf, int row, + boolean hasFocus) { + String newvalue = value.getClass().getName(); // Component type + String name = ((Component)value).getName(); // Component name + if (name != null) newvalue += " (" + name + ")"; // unless null + // Use the wrapped renderer object to do the real work + return renderer.getTreeCellRendererComponent(tree, newvalue, + selected, expanded, + leaf, row, hasFocus); + } + } + + /** + * This main() method demonstrates the use of the ComponentTree class: it + * puts a ComponentTree component in a Frame, and uses the ComponentTree + * to display its own GUI hierarchy. It also adds a TreeSelectionListener + * to display additional information about each component as it is selected + **/ + public static void main(String[] args) { + // Create a frame for the demo, and handle window close requests + JFrame frame = new JFrame("ComponentTree Demo"); + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { System.exit(0); } + }); + + // Create a scroll pane and a "message line" and add them to the + // center and bottom of the frame. + JScrollPane scrollpane = new JScrollPane(); + final JLabel msgline = new JLabel(" "); + frame.getContentPane().add(scrollpane, BorderLayout.CENTER); + frame.getContentPane().add(msgline, BorderLayout.SOUTH); + + // Now create the ComponentTree object, specifying the frame as the + // component whose tree is to be displayed. Also set the tree's font. + JTree tree = new ComponentTree(frame); + tree.setFont(new Font("SansSerif", Font.BOLD, 12)); + + // Only allow a single item in the tree to be selected at once + tree.getSelectionModel().setSelectionMode( + TreeSelectionModel.SINGLE_TREE_SELECTION); + + // Add an event listener for notifications when + // the tree selection state changes. + tree.addTreeSelectionListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + // Tree selections are referred to by "path" + // We only care about the last node in the path + TreePath path = e.getPath(); + Component c = (Component) path.getLastPathComponent(); + // Now we know what component was selected, so + // display some information about it in the message line + if (c.isShowing()) { + Point p = c.getLocationOnScreen(); + msgline.setText("x: " + p.x + " y: " + p.y + + " width: " + c.getWidth() + + " height: " + c.getHeight()); + } + else { + msgline.setText("component is not showing"); + } + } + }); + + // Now that we've set up the tree, add it to the scrollpane + scrollpane.setViewportView(tree); + + // Finally, set the size of the main window, and pop it up. + frame.setSize(600, 400); + frame.setVisible(true); + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/Containers.java b/src/main/java/com/davidflanagan/examples/gui/Containers.java new file mode 100644 index 0000000..5249b7f --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/Containers.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import javax.swing.*; +import java.awt.*; + +/** + * A component subclass that demonstrates nested containers and components. + * It creates the hierarchy shown below, and uses different colors to + * distinguish the different nesting levels of the containers + * + * Containers---panel1----button1 + * | |---panel2----button2 + * | | |----panel3----button3 + * | |------panel4----button4 + * | |----button5 + * |---button6 + */ +public class Containers extends JPanel { + public Containers() { + this.setBackground(Color.white); // This component is white + this.setFont(new Font("Dialog", Font.BOLD, 24)); + + JPanel p1 = new JPanel(); + p1.setBackground(new Color(200, 200, 200)); // Panel1 is darker + this.add(p1); // p1 is contained by this component + p1.add(new JButton("#1")); // Button 1 is contained in p1 + + JPanel p2 = new JPanel(); + p2.setBackground(new Color(150, 150, 150)); // p2 is darker than p2 + p1.add(p2); // p2 is contained in p1 + p2.add(new JButton("#2")); // Button 2 is contained in p2 + + JPanel p3 = new JPanel(); + p3.setBackground(new Color(100, 100, 100)); // p3 is darker than p2 + p2.add(p3); // p3 is contained in p2 + p3.add(new JButton("#3")); // Button 3 is contained in p3 + + JPanel p4 = new JPanel(); + p4.setBackground(new Color(150, 150, 150)); // p4 is darker than p1 + p1.add(p4); // p4 is contained in p1 + p4.add(new JButton("#4")); // Button4 is contained in p4 + p4.add(new JButton("#5")); // Button5 is also contained in p4 + + this.add(new JButton("#6")); // Button6 is contained in this component + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/EventTestPane.java b/src/main/java/com/davidflanagan/examples/gui/EventTestPane.java new file mode 100644 index 0000000..35afda5 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/EventTestPane.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.util.*; + +/** A program that displays all the event that occur in its window */ +public class EventTestPane extends JPanel { + /** The constructor: register the event types we are interested in */ + public EventTestPane() { + // We're interested in all types of events + this.enableEvents(AWTEvent.MOUSE_EVENT_MASK | + AWTEvent.MOUSE_MOTION_EVENT_MASK | + AWTEvent.KEY_EVENT_MASK | + AWTEvent.FOCUS_EVENT_MASK | + AWTEvent.COMPONENT_EVENT_MASK | + AWTEvent.WINDOW_EVENT_MASK); + this.setPreferredSize(new Dimension(500, 400)); + } + + /** + * Display mouse events that don't involve mouse motion. The mousemods() + * method prints modifiers, and is defined below. The other methods + * return additional information about the mouse event. showLine() + * displays a line of text in the window. It is defined at the end of + * this class, along with the paintComponent() method. + **/ + public void processMouseEvent(MouseEvent e) { + String type = null; + switch(e.getID()) { + case MouseEvent.MOUSE_PRESSED: type = "MOUSE_PRESSED"; break; + case MouseEvent.MOUSE_RELEASED: type = "MOUSE_RELEASED"; break; + case MouseEvent.MOUSE_CLICKED: type = "MOUSE_CLICKED"; break; + case MouseEvent.MOUSE_ENTERED: type = "MOUSE_ENTERED"; break; + case MouseEvent.MOUSE_EXITED: type = "MOUSE_EXITED"; break; + } + showLine(mousemods(e) + type + + ": [" + e.getX() + "," + e.getY() + "] " + + "num clicks = " + e.getClickCount() + + (e.isPopupTrigger()?"; is popup trigger":"")); + + // When the mouse enters the component, request keyboard focus so + // we can receive and respond to keyboard events + if (e.getID() == MouseEvent.MOUSE_ENTERED) + requestFocus(); + } + + /** + * Display mouse moved and dragged mouse event. Note that MouseEvent is + * the only event type that has two methods, two EventListener interfaces + * and two adapter classes to handle two distinct categories of events. + * Also, as seen in init(), mouse motion events must be requested + * separately from other mouse event types. + **/ + public void processMouseMotionEvent(MouseEvent e) { + String type = null; + switch(e.getID()) { + case MouseEvent.MOUSE_MOVED: type = "MOUSE_MOVED"; break; + case MouseEvent.MOUSE_DRAGGED: type = "MOUSE_DRAGGED"; break; + } + showLine(mousemods(e) + type + + ": [" + e.getX() + "," + e.getY() + "] " + + "num clicks = " + e.getClickCount() + + (e.isPopupTrigger()?"; is popup trigger":"")); + } + + /** + * Return a string representation of the modifiers for a MouseEvent. + * Note that the methods called here are inherited from InputEvent. + **/ + protected String mousemods(MouseEvent e) { + int mods = e.getModifiers(); + String s = ""; + if (e.isShiftDown()) s += "Shift "; + if (e.isControlDown()) s += "Ctrl "; + if ((mods & InputEvent.BUTTON1_MASK) != 0) s += "Button 1 "; + if ((mods & InputEvent.BUTTON2_MASK) != 0) s += "Button 2 "; + if ((mods & InputEvent.BUTTON3_MASK) != 0) s += "Button 3 "; + return s; + } + + /** + * Display keyboard events. + * + * Note that there are three distinct types of key events, and that key + * events are reported by key code and/or Unicode character. KEY_PRESSED + * and KEY_RELEASED events are generated for all key strokes. KEY_TYPED + * events are only generated when a key stroke produces a Unicode + * character; these events do not report a key code. If isActionKey() + * returns true, then the key event reports only a key code, because the + * key that was pressed or released (such as a function key) has no + * corresponding Unicode character. Key codes can be interpreted by using + * the many VK_ constants defined by the KeyEvent class, or they can be + * converted to strings using the static getKeyText() method as we do + * here. + **/ + public void processKeyEvent(KeyEvent e) { + String eventtype, modifiers, code, character; + switch(e.getID()) { + case KeyEvent.KEY_PRESSED: eventtype = "KEY_PRESSED"; break; + case KeyEvent.KEY_RELEASED: eventtype = "KEY_RELEASED"; break; + case KeyEvent.KEY_TYPED: eventtype = "KEY_TYPED"; break; + default: eventtype = "UNKNOWN"; + } + + // Convert the list of modifier keys to a string + modifiers = KeyEvent.getKeyModifiersText(e.getModifiers()); + + // Get string and numeric versions of the key code, if any. + if (e.getID() == KeyEvent.KEY_TYPED) code = ""; + else code = "Code=" + KeyEvent.getKeyText(e.getKeyCode()) + + " (" + e.getKeyCode() + ")"; + + // Get string and numeric versions of the Unicode character, if any. + if (e.isActionKey()) character = ""; + else character = "Character=" + e.getKeyChar() + + " (Unicode=" + ((int)e.getKeyChar()) + ")"; + + // Display it all. + showLine(eventtype + ": " + modifiers + " " + code + " " + character); + } + + /** + * Display keyboard focus events. Focus can be permanently gained or + * lost, or temporarily transferred to or from a component. + **/ + public void processFocusEvent(FocusEvent e) { + if (e.getID() == FocusEvent.FOCUS_GAINED) + showLine("FOCUS_GAINED" + (e.isTemporary()?" (temporary)":"")); + else + showLine("FOCUS_LOST" + (e.isTemporary()?" (temporary)":"")); + } + + /** Display Component events. */ + public void processComponentEvent(ComponentEvent e) { + switch(e.getID()) { + case ComponentEvent.COMPONENT_MOVED: + showLine("COMPONENT_MOVED"); break; + case ComponentEvent.COMPONENT_RESIZED: + showLine("COMPONENT_RESIZED");break; + case ComponentEvent.COMPONENT_HIDDEN: + showLine("COMPONENT_HIDDEN"); break; + case ComponentEvent.COMPONENT_SHOWN: + showLine("COMPONENT_SHOWN"); break; + } + } + + /** Display Window events. Note the special handling of WINDOW_CLOSING */ + public void processWindowEvent(WindowEvent e) { + switch(e.getID()) { + case WindowEvent.WINDOW_OPENED: showLine("WINDOW_OPENED"); break; + case WindowEvent.WINDOW_CLOSED: showLine("WINDOW_CLOSED"); break; + case WindowEvent.WINDOW_CLOSING: showLine("WINDOW_CLOSING"); break; + case WindowEvent.WINDOW_ICONIFIED: showLine("WINDOW_ICONIFIED"); break; + case WindowEvent.WINDOW_DEICONIFIED: + showLine("WINDOW_DEICONIFIED"); break; + case WindowEvent.WINDOW_ACTIVATED: + showLine("WINDOW_ACTIVATED"); break; + case WindowEvent.WINDOW_DEACTIVATED: + showLine("WINDOW_DEACTIVATED"); break; + } + + // If the user requested a window close, quit the program. + // But first display a message, force it to be visible, and make + // sure the user has time to read it. + if (e.getID() == WindowEvent.WINDOW_CLOSING) { + showLine("WINDOW_CLOSING event received."); + showLine("Application will exit in 5 seconds"); + // Force the updates to appear now. + update(this.getGraphics()); + // Wait five seconds + try {Thread.sleep(5000);} catch (InterruptedException ie) { ; } + // Exit now + System.exit(0); + } + } + + /** The list of lines to display in the window */ + protected Vector lines = new Vector(); + + /** Add a new line to the list of lines, and request redisplay */ + protected void showLine(String s) { + if (lines.size() == 20) lines.removeElementAt(0); + lines.addElement(s); + repaint(); + } + + /** This method repaints the text in the window */ + public void paintComponent(Graphics g) { + for(int i = 0; i < lines.size(); i++) + g.drawString((String)lines.elementAt(i), 20, i*16 + 50); + } + + public boolean isOpaque() { return false; } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/FlowLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/FlowLayoutPane.java new file mode 100644 index 0000000..dd56ac8 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/FlowLayoutPane.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import javax.swing.*; + +public class FlowLayoutPane extends JPanel { + public FlowLayoutPane() { + // Use a FlowLayout layout manager. Left justify rows. + // Leave 10 pixels of horizontal and vertical space between components. + this.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 10)); + + // Add some buttons to demonstrate the layout. + String spaces = ""; // Used to make the buttons different + for(int i = 1; i <= 9; i++) { + this.add(new JButton("Button #" + i + spaces)); + spaces += " "; + } + + // Give ourselves a default size + this.setPreferredSize(new Dimension(500, 200)); + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/FontChooser.java b/src/main/java/com/davidflanagan/examples/gui/FontChooser.java new file mode 100644 index 0000000..97f7360 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/FontChooser.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.davidflanagan.examples.gui.ItemChooser; + +/** + * This is a JDialog subclass that allows the user to select a font, in any + * style and size, from the list of available fonts on the system. The + * dialog is modal. Display it with show(); this method does not return + * until the user dismisses the dialog. When show() returns, call + * getSelectedFont() to obtain the user's selection. If the user clicked the + * dialog's "Cancel" button, getSelectedFont() will return null. + **/ +public class FontChooser extends JDialog { + // These fields define the component properties + String family; // The name of the font family + int style; // The font style + int size; // The font size + Font selectedFont; // The Font they correspond to + + // This is the list of all font families on the system + String[] fontFamilies; + + // The various Swing components used in the dialog + ItemChooser families, styles, sizes; + JTextArea preview; + JButton okay, cancel; + + // The names to appear in the "Style" menu + static final String[] styleNames = new String[] { + "Plain", "Italic", "Bold", "BoldItalic" + }; + // The style values that correspond to those names + static final Integer[] styleValues = new Integer[] { + new Integer(Font.PLAIN), new Integer(Font.ITALIC), + new Integer(Font.BOLD), new Integer(Font.BOLD+Font.ITALIC) + }; + // The size "names" to appear in the size menu + static final String[] sizeNames = new String[] { + "8", "10", "12", "14", "18", "20", "24", "28", "32", + "40", "48", "56", "64", "72" + }; + + // This is the default preview string displayed in the dialog box + static final String defaultPreviewString = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" + + "abcdefghijklmnopqrstuvwxyz\n" + + "1234567890!@#$%^&*()_-=+[]{}<,.>\n" + + "The quick brown fox jumps over the lazy dog"; + + /** Create a font chooser dialog for the specified frame. */ + public FontChooser(Frame owner) { + super(owner, "Choose a Font"); // Set dialog frame and title + + // This dialog must be used as a modal dialog. In order to be used + // as a modeless dialog, it would have to fire a PropertyChangeEvent + // whenever the selected font changed, so that applications could be + // notified of the user's selections. + setModal(true); + + // Figure out what fonts are available on the system + GraphicsEnvironment env = + GraphicsEnvironment.getLocalGraphicsEnvironment(); + fontFamilies = env.getAvailableFontFamilyNames(); + + // Set initial values for the properties + family = fontFamilies[0]; + style = Font.PLAIN; + size = 18; + selectedFont = new Font(family, style, size); + + // Create ItemChooser objects that allow the user to select font + // family, style, and size. + families = new ItemChooser("Family", fontFamilies, null, 0, + ItemChooser.COMBOBOX); + styles = new ItemChooser("Style", styleNames, styleValues, 0, + ItemChooser.COMBOBOX); + sizes = new ItemChooser("Size", sizeNames,null,4,ItemChooser.COMBOBOX); + + // Now register event listeners to handle selections + families.addItemChooserListener(new ItemChooser.Listener() { + public void itemChosen(ItemChooser.Event e) { + setFontFamily((String)e.getSelectedValue()); + } + }); + styles.addItemChooserListener(new ItemChooser.Listener() { + public void itemChosen(ItemChooser.Event e) { + setFontStyle(((Integer)e.getSelectedValue()).intValue()); + } + }); + sizes.addItemChooserListener(new ItemChooser.Listener() { + public void itemChosen(ItemChooser.Event e) { + setFontSize(Integer.parseInt((String)e.getSelectedValue())); + } + }); + + // Create a component to preview the font. + preview = new JTextArea(defaultPreviewString, 5, 40); + preview.setFont(selectedFont); + + // Create buttons to dismiss the dialog, and set handlers on them + okay = new JButton("Okay"); + cancel = new JButton("Cancel"); + okay.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { hide(); } + }); + cancel.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + selectedFont = null; + hide(); + } + }); + + // Put the ItemChoosers in a Box + Box choosersBox = Box.createHorizontalBox(); + choosersBox.add(Box.createHorizontalStrut(15)); + choosersBox.add(families); + choosersBox.add(Box.createHorizontalStrut(15)); + choosersBox.add(styles); + choosersBox.add(Box.createHorizontalStrut(15)); + choosersBox.add(sizes); + choosersBox.add(Box.createHorizontalStrut(15)); + choosersBox.add(Box.createGlue()); + + // Put the dismiss buttons in another box + Box buttonBox = Box.createHorizontalBox(); + buttonBox.add(Box.createGlue()); + buttonBox.add(okay); + buttonBox.add(Box.createGlue()); + buttonBox.add(cancel); + buttonBox.add(Box.createGlue()); + + // Put the choosers at the top, the buttons at the bottom, and + // the preview in the middle. + Container contentPane = getContentPane(); + contentPane.add(new JScrollPane(preview), BorderLayout.CENTER); + contentPane.add(choosersBox, BorderLayout.NORTH); + contentPane.add(buttonBox, BorderLayout.SOUTH); + + // Set the dialog size based on the component size. + pack(); + } + + /** + * Call this method after show() to obtain the user's selection. If the + * user used the "Cancel" button, this will return null + **/ + public Font getSelectedFont() { return selectedFont; } + + + // These are other property getter methods + public String getFontFamily() { return family; } + public int getFontStyle() { return style; } + public int getFontSize() { return size; } + + // The property setter methods are a little more complicated. + // Note that none of these setter methods update the corresponding + // ItemChooser components as they ought to. + public void setFontFamily(String name) { + family = name; + changeFont(); + } + public void setFontStyle(int style) { + this.style = style; + changeFont(); + } + public void setFontSize(int size) { + this.size = size; + changeFont(); + } + public void setSelectedFont(Font font) { + selectedFont = font; + family = font.getFamily(); + style = font.getStyle(); + size = font.getSize(); + preview.setFont(font); + } + + // This method is called when the family, style, or size changes + protected void changeFont() { + selectedFont = new Font(family, style, size); + preview.setFont(selectedFont); + } + + // Override this inherited method to prevent anyone from making us modeless + public boolean isModal() { return true; } + + /** This inner class demonstrates the use of FontChooser */ + public static class Demo { + public static void main(String[] args) { + // Create some components and a FontChooser dialog + final JFrame frame = new JFrame("demo"); + final JButton button = new JButton("Push Me!"); + final FontChooser chooser = new FontChooser(frame); + + // Handle button clicks + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Pop up the dialog + chooser.show(); + // Get the user's selection + Font font = chooser.getSelectedFont(); + // If not cancelled, set the button font + if (font != null) button.setFont(font); + } + }); + + // Display the demo + frame.getContentPane().add(button); + frame.setSize(200, 100); + frame.show(); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/GUIResourceBundle.java b/src/main/java/com/davidflanagan/examples/gui/GUIResourceBundle.java new file mode 100644 index 0000000..f6f33a9 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/GUIResourceBundle.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.io.*; +import java.util.*; +import java.awt.*; + +/** + * This class extends ResourceBundle and adds methods to retrieve types of + * resources commonly used in GUIs. Additionally, it adds extensibility + * by allowing ResourceParser objects to be registered to parse other + * resource types. + **/ +public class GUIResourceBundle extends ResourceBundle { + // The root object. Required to parse certain resource types like Commands + Object root; + + // The resource bundle that actually contains the textual resources + // This class is a wrapper around this bundle + ResourceBundle bundle; + + /** Create a GUIResourceBundle wrapper around a specified bundle */ + public GUIResourceBundle(Object root, ResourceBundle bundle) { + this.root = root; + this.bundle = bundle; + } + + /** + * Load a named bundle and create a GUIResourceBundle around it. This + * constructor takes advantage of the internationalization features of + * the ResourceBundle.getBundle() method. + **/ + public GUIResourceBundle(Object root, String bundleName) + throws MissingResourceException + { + this.root = root; + this.bundle = ResourceBundle.getBundle(bundleName); + } + + /** + * Create a PropertyResourceBundle from the specified stream and then + * create a GUIResourceBundle wrapper for it + **/ + public GUIResourceBundle(Object root, InputStream propertiesStream) + throws IOException + { + this.root = root; + this.bundle = new PropertyResourceBundle(propertiesStream); + } + + /** + * Create a PropertyResourceBundle from the specified properties file and + * then create a GUIResourceBundle wrapper for it. + **/ + public GUIResourceBundle(Object root, File propertiesFile) + throws IOException + { + this(root, new FileInputStream(propertiesFile)); + } + + /** This is one of the abstract methods of ResourceBundle */ + public Enumeration getKeys() { return bundle.getKeys(); } + + /** This is the other abstract method of ResourceBundle */ + protected Object handleGetObject(String key) + throws MissingResourceException + { + return bundle.getObject(key); // simply defer to the wrapped bundle + } + + /** This is a property accessor method for our root object */ + public Object getRoot() { return root; } + + /** + * This method is like the inherited getString() method, except that + * when the named resource is not found, it returns the specified default + * instead of throwing an exception + **/ + public String getString(String key, String defaultValue) { + try { return bundle.getString(key); } + catch(MissingResourceException e) { return defaultValue; } + } + + /** + * Look up the named resource and parse it as a list of strings separated + * by spaces, tabs, or commas. + **/ + public java.util.List getStringList(String key) + throws MissingResourceException + { + String s = getString(key); + StringTokenizer t = new StringTokenizer(s, ", \t", false); + ArrayList list = new ArrayList(); + while(t.hasMoreTokens()) list.add(t.nextToken()); + return list; + } + + /** Like above, but return a default instead of throwing an exception */ + public java.util.List getStringList(String key, + java.util.List defaultValue) { + try { return getStringList(key); } + catch(MissingResourceException e) { return defaultValue; } + } + + /** Look up the named resource and try to interpret it as a boolean. */ + public boolean getBoolean(String key) throws MissingResourceException { + String s = bundle.getString(key); + s = s.toLowerCase(); + if (s.equals("true")) return true; + else if (s.equals("false")) return false; + else if (s.equals("yes")) return true; + else if (s.equals("no")) return false; + else if (s.equals("on")) return true; + else if (s.equals("off")) return false; + else { + throw new MalformedResourceException("boolean", key); + } + } + + /** As above, but return the default instead of throwing an exception */ + public boolean getBoolean(String key, boolean defaultValue) { + try { return getBoolean(key); } + catch(MissingResourceException e) { + if (e instanceof MalformedResourceException) + System.err.println("WARNING: " + e.getMessage()); + return defaultValue; + } + } + + /** Like getBoolean(), but for integers */ + public int getInt(String key) throws MissingResourceException { + String s = bundle.getString(key); + + try { + // Use decode() instead of parseInt() so we support octal + // and hexadecimal numbers + return Integer.decode(s).intValue(); + } catch (NumberFormatException e) { + throw new MalformedResourceException("int", key); + } + } + + /** As above, but with a default value */ + public int getInt(String key, int defaultValue) { + try { return getInt(key); } + catch(MissingResourceException e) { + if (e instanceof MalformedResourceException) + System.err.println("WARNING: " + e.getMessage()); + return defaultValue; + } + } + + /** Return a resource of type double */ + public double getDouble(String key) throws MissingResourceException { + String s = bundle.getString(key); + + try { + return Double.parseDouble(s); + } catch (NumberFormatException e) { + throw new MalformedResourceException("double", key); + } + } + + /** As above, but with a default value */ + public double getDouble(String key, double defaultValue) { + try { return getDouble(key); } + catch(MissingResourceException e) { + if (e instanceof MalformedResourceException) + System.err.println("WARNING: " + e.getMessage()); + return defaultValue; + } + } + + /** Look up the named resource and convert to a Font */ + public Font getFont(String key) throws MissingResourceException { + // Font.decode() always returns a Font object, so we can't check + // whether the resource value was well-formed or not. + return Font.decode(bundle.getString(key)); + } + + /** As above, but with a default value */ + public Font getFont(String key, Font defaultValue) { + try { return getFont(key); } + catch (MissingResourceException e) { return defaultValue; } + } + + /** Look up the named resource, and convert to a Color */ + public Color getColor(String key) throws MissingResourceException { + try { + return Color.decode(bundle.getString(key)); + } + catch (NumberFormatException e) { + // It would be useful to try to parse color names here as well + // as numeric color specifications + throw new MalformedResourceException("Color", key); + } + } + + /** As above, but with a default value */ + public Color getColor(String key, Color defaultValue) { + try { return getColor(key); } + catch(MissingResourceException e) { + if (e instanceof MalformedResourceException) + System.err.println("WARNING: " + e.getMessage()); + return defaultValue; + } + } + + /** A hashtable for mapping resource types to resource parsers */ + static HashMap parsers = new HashMap(); + + /** An extension mechanism: register a parser for new resource types */ + public static void registerResourceParser(ResourceParser parser) { + // Ask the ResourceParser what types it can parse + Class[] supportedTypes = parser.getResourceTypes(); + // Register it in the hashtable for each of those types + for(int i = 0; i < supportedTypes.length; i++) + parsers.put(supportedTypes[i], parser); + } + + /** Look up a ResourceParser for the specified resource type */ + public static ResourceParser getResourceParser(Class type) { + return (ResourceParser) parsers.get(type); + } + + /** + * Look for a ResourceParser for the named type, and if one is found, + * ask it to parse and return the named resource + **/ + public Object getResource(String key, Class type) + throws MissingResourceException + { + // Get a parser for the specified type + ResourceParser parser = (ResourceParser)parsers.get(type); + if (parser == null) + throw new MissingResourceException( + "No ResourceParser registered for " + + type.getName() + " resources", + type.getName(), key); + + try { // Ask the parser to parse the resource + return parser.parse(this, key, type); + } + catch(MissingResourceException e) { + throw e; // Rethrow MissingResourceException exceptions + } + catch(Exception e) { + // If any other type of exception occurs, convert it to + // a MalformedResourceException + String msg = "Malformed " + type.getName() + " resource: " + + key + ": " + e.getMessage(); + throw new MalformedResourceException(msg, type.getName(), key); + } + } + + /** + * Like the 2-argument version of getResource, but return a default value + * instead of throwing a MissingResourceException + **/ + public Object getResource(String key, Class type, Object defaultValue) { + try { return getResource(key, type); } + catch (MissingResourceException e) { + if (e instanceof MalformedResourceException) + System.err.println("WARNING: " + e.getMessage()); + return defaultValue; + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/GridBagLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/GridBagLayoutPane.java new file mode 100644 index 0000000..217542c --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/GridBagLayoutPane.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import javax.swing.*; + +public class GridBagLayoutPane extends JPanel { + public GridBagLayoutPane() { + // Create and specify a layout manager + this.setLayout(new GridBagLayout()); + + // Create a constraints object, and specify some default values + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; // components grow in both dimensions + c.insets = new Insets(5,5,5,5); // 5-pixel margins on all sides + + // Create and add a bunch of buttons, specifying different grid + // position, and size for each. + // Give the first button a resize weight of 1.0 and all others + // a weight of 0.0. The first button will get all extra space. + c.gridx = 0; c.gridy = 0; c.gridwidth = 4; c.gridheight=4; + c.weightx = c.weighty = 1.0; + this.add(new JButton("Button #1"), c); + + c.gridx = 4; c.gridy = 0; c.gridwidth = 1; c.gridheight=1; + c.weightx = c.weighty = 0.0; + this.add(new JButton("Button #2"), c); + + c.gridx = 4; c.gridy = 1; c.gridwidth = 1; c.gridheight=1; + this.add(new JButton("Button #3"), c); + + c.gridx = 4; c.gridy = 2; c.gridwidth = 1; c.gridheight=2; + this.add(new JButton("Button #4"), c); + + c.gridx = 0; c.gridy = 4; c.gridwidth = 1; c.gridheight=1; + this.add(new JButton("Button #5"), c); + + c.gridx = 2; c.gridy = 4; c.gridwidth = 1; c.gridheight=1; + this.add(new JButton("Button #6"), c); + + c.gridx = 3; c.gridy = 4; c.gridwidth = 2; c.gridheight=1; + this.add(new JButton("Button #7"), c); + + c.gridx = 1; c.gridy = 5; c.gridwidth = 1; c.gridheight=1; + this.add(new JButton("Button #8"), c); + + c.gridx = 3; c.gridy = 5; c.gridwidth = 1; c.gridheight=1; + this.add(new JButton("Button #9"), c); + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/GridLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/GridLayoutPane.java new file mode 100644 index 0000000..e644d2d --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/GridLayoutPane.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import javax.swing.*; + +public class GridLayoutPane extends JPanel { + public GridLayoutPane() { + // Layout components into a grid three columns wide, with the number + // of rows depending on the number of components. Leave 10 pixels + // of horizontal and vertical space between components + this.setLayout(new GridLayout(0, 3, 10, 10)); + // Add some components + for(int i = 1; i <= 12; i++) this.add(new JButton("Button #" + i)); + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ItemChooser.java b/src/main/java/com/davidflanagan/examples/gui/ItemChooser.java new file mode 100644 index 0000000..ac0dd70 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ItemChooser.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.border.*; +import java.util.*; + +/** + * This class is a Swing component that presents a choice to the user. It + * allows the choice to be presented in a JList, in a JComboBox, or with a + * bordered group of JRadioButton components. Additionally, it displays the + * name of the choice with a JLabel. It allows an arbitrary value to be + * associated with each possible choice. Note that this component only allows + * one item to be selected at a time. Multiple selections are not supported. + **/ +public class ItemChooser extends JPanel { + // These fields hold property values for this component + String name; // The overall name of the choice + String[] labels; // The text for each choice option + Object[] values; // Arbitrary values associated with each option + int selection; // The selected choice + int presentation; // How the choice is presented + + // These are the legal values for the presentation field + public static final int LIST = 1; + public static final int COMBOBOX = 2; + public static final int RADIOBUTTONS = 3; + + // These components are used for each of the 3 possible presentations + JList list; // One type of presentation + JComboBox combobox; // Another type of presentation + JRadioButton[] radiobuttons; // Yet another type + + // The list of objects that are interested in our state + ArrayList listeners = new ArrayList(); + + // The constructor method sets everything up + public ItemChooser(String name, String[] labels, Object[] values, + int defaultSelection, int presentation) + { + // Copy the constructor arguments to instance fields + this.name = name; + this.labels = labels; + this.values = values; + this.selection = defaultSelection; + this.presentation = presentation; + + // If no values were supplied, use the labels + if (values == null) this.values = labels; + + // Now create content and event handlers based on presentation type + switch(presentation) { + case LIST: initList(); break; + case COMBOBOX: initComboBox(); break; + case RADIOBUTTONS: initRadioButtons(); break; + } + } + + // Initialization for JList presentation + void initList() { + list = new JList(labels); // Create the list + list.setSelectedIndex(selection); // Set initial state + + // Handle state changes + list.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + ItemChooser.this.select(list.getSelectedIndex()); + } + }); + + // Lay out list and name label vertically + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); // vertical + this.add(new JLabel(name)); // Display choice name + this.add(new JScrollPane(list)); // Add the JList + } + + // Initialization for JComboBox presentation + void initComboBox() { + combobox = new JComboBox(labels); // Create the combo box + combobox.setSelectedIndex(selection); // Set initial state + + // Handle changes to the state + combobox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + ItemChooser.this.select(combobox.getSelectedIndex()); + } + }); + + // Lay out combo box and name label horizontally + this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + this.add(new JLabel(name)); + this.add(combobox); + } + + // Initialization for JRadioButton presentation + void initRadioButtons() { + // Create an array of mutually exclusive radio buttons + radiobuttons = new JRadioButton[labels.length]; // the array + ButtonGroup radioButtonGroup = new ButtonGroup(); // used for exclusion + ChangeListener listener = new ChangeListener() { // A shared listener + public void stateChanged(ChangeEvent e) { + JRadioButton b = (JRadioButton)e.getSource(); + if (b.isSelected()) { + // If we received this event because a button was + // selected, then loop through the list of buttons to + // figure out the index of the selected one. + for(int i = 0; i < radiobuttons.length; i++) { + if (radiobuttons[i] == b) { + ItemChooser.this.select(i); + return; + } + } + } + } + }; + + // Display the choice name in a border around the buttons + this.setBorder(new TitledBorder(new EtchedBorder(), name)); + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + // Create the buttons, add them to the button group, and specify + // the event listener for each one. + for(int i = 0; i < labels.length; i++) { + radiobuttons[i] = new JRadioButton(labels[i]); + if (i == selection) radiobuttons[i].setSelected(true); + radiobuttons[i].addChangeListener(listener); + radioButtonGroup.add(radiobuttons[i]); + this.add(radiobuttons[i]); + } + } + + // These simple property accessor methods just return field values + // These are read-only properties. The values are set by the constructor + // and may not be changed. + public String getName() { return name; } + public int getPresentation() { return presentation; } + public String[] getLabels() { return labels; } + public Object[] getValues() { return values; } + + /** Return the index of the selected item */ + public int getSelectedIndex() { return selection; } + + /** Return the object associated with the selected item */ + public Object getSelectedValue() { return values[selection]; } + + /** + * Set the selected item by specifying its index. Calling this + * method changes the on-screen display but does not generate events. + **/ + public void setSelectedIndex(int selection) { + switch(presentation) { + case LIST: list.setSelectedIndex(selection); break; + case COMBOBOX: combobox.setSelectedIndex(selection); break; + case RADIOBUTTONS: radiobuttons[selection].setSelected(true); break; + } + this.selection = selection; + } + + /** + * This internal method is called when the selection changes. It stores + * the new selected index, and fires events to any registered listeners. + * The event listeners registered on the JList, JComboBox, or JRadioButtons + * all call this method. + **/ + protected void select(int selection) { + this.selection = selection; // Store the new selected index + if (!listeners.isEmpty()) { // If there are any listeners registered + // Create an event object to describe the selection + Event e = + new Event(this, selection, values[selection]); + // Loop through the listeners using an Iterator + for(Iterator i = listeners.iterator(); i.hasNext();) { + Listener l = (Listener)i.next(); + l.itemChosen(e); // Notify each listener of the selection + } + } + } + + // These methods are for event listener registration and deregistration + public void addItemChooserListener(Listener l) { + listeners.add(l); + } + public void removeItemChooserListener(Listener l) { + listeners.remove(l); + } + + /** + * This inner class defines the event type generated by ItemChooser objects + * The inner class name is Event, so the full name is ItemChooser.Event + **/ + public static class Event extends EventObject { + int selectedIndex; // index of the selected item + Object selectedValue; // the value associated with it + public Event(ItemChooser source, + int selectedIndex, Object selectedValue) { + super(source); + this.selectedIndex = selectedIndex; + this.selectedValue = selectedValue; + } + + public ItemChooser getItemChooser() { return (ItemChooser)getSource();} + public int getSelectedIndex() { return selectedIndex; } + public Object getSelectedValue() { return selectedValue; } + } + + /** + * This inner interface must be implemented by any object that wants to be + * notified when the current selection in a ItemChooser component changes. + **/ + public interface Listener extends EventListener { + public void itemChosen(Event e); + } + + /** + * This inner class is a simple demonstration of the ItemChooser component + * It uses command-line arguments as ItemChooser labels and values. + **/ + public static class Demo { + public static void main(String[] args) { + // Create a window, arrange to handle close requests + final JFrame frame = new JFrame("ItemChooser Demo"); + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) {System.exit(0);} + }); + + // A "message line" to display results in + final JLabel msgline = new JLabel(" "); + + // Create a panel holding three ItemChooser components + JPanel chooserPanel = new JPanel(); + final ItemChooser c1 = new ItemChooser("Choice #1", args, null, 0, + ItemChooser.LIST); + final ItemChooser c2 = new ItemChooser("Choice #2", args, null, 0, + ItemChooser.COMBOBOX); + final ItemChooser c3 = new ItemChooser("Choice #3", args, null, 0, + ItemChooser.RADIOBUTTONS); + + // An event listener that displays changes on the message line + Listener l = new Listener() { + public void itemChosen(Event e) { + msgline.setText(e.getItemChooser().getName() + ": " + + e.getSelectedIndex() + ": " + + e.getSelectedValue()); + } + }; + c1.addItemChooserListener(l); + c2.addItemChooserListener(l); + c3.addItemChooserListener(l); + + // Instead of tracking every change with a ItemChooser.Listener, + // applications can also just query the current state when + // they need it. Here's a button that does that. + JButton report = new JButton("Report"); + report.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Note the use of multi-line italic HTML text + // with the JOptionPane message dialog box. + String msg = "" + + c1.getName() + ": " + c1.getSelectedValue() + "
"+ + c2.getName() + ": " + c2.getSelectedValue() + "
"+ + c3.getName() + ": " + c3.getSelectedValue() + "
"; + JOptionPane.showMessageDialog(frame, msg); + } + }); + + // Add the 3 ItemChooser objects, and the Button to the panel + chooserPanel.add(c1); + chooserPanel.add(c2); + chooserPanel.add(c3); + chooserPanel.add(report); + + // Add the panel and the message line to the window + Container contentPane = frame.getContentPane(); + contentPane.add(chooserPanel, BorderLayout.CENTER); + contentPane.add(msgline, BorderLayout.SOUTH); + + // Set the window size and pop it up. + frame.pack(); + frame.show(); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/MalformedResourceException.java b/src/main/java/com/davidflanagan/examples/gui/MalformedResourceException.java new file mode 100644 index 0000000..5476460 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/MalformedResourceException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.util.MissingResourceException; + +/** + * This subclass of MissingResourceException signals that a resource value + * was present, but could not be properly parsed or otherwise converted to + * the desired type. + **/ +public class MalformedResourceException extends MissingResourceException { + public MalformedResourceException(String msg, String type, String key){ + super(msg, type, key); + } + // Convenience constructors: automatically generate exception message + public MalformedResourceException(String type, String key){ + super("Malformed " + type + " resource: " + key, type, key); + } + public MalformedResourceException(Class type, String key){ + this(type.getName(), key); + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/MenuBarParser.java b/src/main/java/com/davidflanagan/examples/gui/MenuBarParser.java new file mode 100644 index 0000000..4a609c5 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/MenuBarParser.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import javax.swing.*; +import java.util.*; + +/** + * Parse a JMenuBar from a ResourceBundle. A menubar is represented + * simply as a list of menu property names. E.g.: + * menubar: menu.file menu.edit menu.view menu.help + **/ +public class MenuBarParser implements ResourceParser { + static final Class[] supportedTypes = new Class[] { JMenuBar.class }; + public Class[] getResourceTypes() { return supportedTypes; } + + public Object parse(GUIResourceBundle bundle, String key, Class type) + throws MissingResourceException + { + // Get the value of the key as a list of strings + List menuList = bundle.getStringList(key); + + // Create a MenuBar + JMenuBar menubar = new JMenuBar(); + + // Create a JMenu for each of the menu property names, + // and add it to the bar + int nummenus = menuList.size(); + for(int i = 0; i < nummenus; i++) { + menubar.add((JMenu) bundle.getResource((String)menuList.get(i), + JMenu.class)); + } + + return menubar; + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/MenuParser.java b/src/main/java/com/davidflanagan/examples/gui/MenuParser.java new file mode 100644 index 0000000..a40681f --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/MenuParser.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import com.davidflanagan.examples.reflect.*; +import java.awt.event.*; +import javax.swing.*; +import java.util.StringTokenizer; + +/** + * This class parses a JMenu or JPopupMenu from textual descriptions found in + * a GUIResourceBundle. The grammar is straightforward: the menu label + * followed by a colon and a list of menu items. Menu items that begin with + * a '>' character are submenus. Menu items that begin with a '-' character + * are separators. All other items are action names. + **/ +public class MenuParser implements ResourceParser { + static final Class[] supportedTypes = new Class[] { + JMenu.class, JPopupMenu.class // This class handles two resource types + }; + + public Class[] getResourceTypes() { return supportedTypes; } + + public Object parse(GUIResourceBundle bundle, String key, Class type) + throws java.util.MissingResourceException + { + // Get the string value of the key + String menudef = bundle.getString(key); + + // Break it up into words, ignoring whitespace, colons and commas + StringTokenizer st = new StringTokenizer(menudef, " \t:,"); + + // The first word is the label of the menu + String menuLabel = st.nextToken(); + + // Create either a JMenu or JPopupMenu + JMenu menu = null; + JPopupMenu popup = null; + if (type == JMenu.class) menu = new JMenu(menuLabel); + else popup = new JPopupMenu(menuLabel); + + // Then loop through the rest of the words, creating a JMenuItem + // for each one. Accumulate these items in a list + while(st.hasMoreTokens()) { + String item = st.nextToken(); // the next word + char firstchar = item.charAt(0); // determines type of menu item + switch(firstchar) { + case '-': // words beginning with - add a separator to the menu + if (menu != null) menu.addSeparator(); + else popup.addSeparator(); + break; + case '>': // words beginning with > are submenu names + // strip off the > character, and recurse to parse the submenu + item = item.substring(1); + // Parse a submenu and add it to the list of items + JMenu submenu = (JMenu)parse(bundle, item, JMenu.class); + if (menu != null) menu.add(submenu); + else popup.add(submenu); + break; + case '!': // words beginning with ! are action names + item = item.substring(1); // strip off the ! character + /* falls through */ // fall through to the next case + default: // By default all other words are taken as action names + // Look up the named action and add it to the menu + Action action = (Action)bundle.getResource(item, Action.class); + if (menu != null) menu.add(action); + else popup.add(action); + break; + } + } + + // Finally, return the menu or the popup menu + if (menu != null) return menu; + else return popup; + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/NullLayoutPane.java b/src/main/java/com/davidflanagan/examples/gui/NullLayoutPane.java new file mode 100644 index 0000000..cf9deb2 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/NullLayoutPane.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import javax.swing.*; + +public class NullLayoutPane extends JPanel { + public NullLayoutPane() { + // Get rid of the default layout manager. + // We'll arrange the components ourselves. + this.setLayout(null); + + // Create some buttons and set their sizes and positions explicitly + for(int i = 1; i <= 9; i++) { + JButton b = new JButton("Button #" + i); + b.setBounds(i*30, i*20, 125, 30); // use reshape() in Java 1.0 + this.add(b); + } + } + + // Specify how big the panel should be. + public Dimension getPreferredSize() { return new Dimension(425, 250); } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/PrintableDocument.java b/src/main/java/com/davidflanagan/examples/gui/PrintableDocument.java new file mode 100644 index 0000000..3666e8a --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/PrintableDocument.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.awt.print.*; +import java.awt.geom.*; +import java.awt.font.*; +import javax.swing.*; +import javax.swing.text.*; +import java.util.*; + +/** + * This class implements the Pageable and Printable interfaces and allows + * the contents of any JTextComponent to be printed using the java.awt.print + * printing API. + **/ +public class PrintableDocument implements Pageable, Printable { + View root; // The root View to be printed + PageFormat format; // Paper plus page orientation + int numPages; // How many pages in the document + double printX, printY; // coordinates of upper-left of print area + double printWidth; // Width of the printable area + double printHeight; // Height of the printable area + Rectangle drawRect; // The rectangle in which the document is painted + + // How lenient are we with the bottom margin in widow/orphan prevention? + static final double MARGIN_ADJUST = .97; + + // The font we use for printing page numbers + static final Font headerFont = new Font("Serif", Font.PLAIN, 12); + + /** + * This constructor allows printing the contents of any JTextComponent + * using a default PageFormat + */ + public PrintableDocument(JTextComponent textComponent) { + this(textComponent, new PageFormat()); + } + + /** + * This constructor allows the contents of any JTextComponent to be + * printed, using any specified PageFormat object + **/ + public PrintableDocument(JTextComponent textComponent, PageFormat format) { + // Remember the page format, and ask it for the printable area + this.format = format; + this.printX = format.getImageableX(); + this.printY = format.getImageableY(); + this.printWidth = format.getImageableWidth(); + this.printHeight = format.getImageableHeight(); + double paperWidth = format.getWidth(); + + // Get the document and its root Element from the text component + Document document = textComponent.getDocument(); + Element rootElement = document.getDefaultRootElement(); + // Get the EditorKit and its ViewFactory from the text component + EditorKit editorKit =textComponent.getUI().getEditorKit(textComponent); + ViewFactory viewFactory = editorKit.getViewFactory(); + + // Use the ViewFactory to create a root View object for the document + // This is the object we'll print. + root = viewFactory.create(rootElement); + + // The Swing text architecture requires us to call setParent() on + // our root View before we use it for anything. In order to do this, + // we need a View object that can serve as the parent. We use a + // custom implementation defined below. + root.setParent(new ParentView(root, viewFactory, textComponent)); + + // Tell the view how wide the page is; it has to format itself + // to fit within this width. The height doesn't really matter here + root.setSize((float)printWidth, (float)printHeight); + + // Now that the view has formatted itself for the specified width, + // Ask it how tall it is. + double documentHeight = root.getPreferredSpan(View.Y_AXIS); + + // Set up the rectangle that tells the view where to draw itself + // We'll use it in other methods of this class. + drawRect = new Rectangle((int)printX, (int)printY, + (int)printWidth, (int)documentHeight); + + // Now if the document is taller than one page, we have to + // figure out where the page breaks are. + if (documentHeight > printHeight) paginate(root, drawRect); + + // Once we've broken it into pages, figure out how man pages. + numPages = pageLengths.size() + 1; + } + + // This is the starting offset of the page we're currently working on + double pageStart = 0; + + /** + * This method loops through the children of the specified view, + * recursing as necessary, and inserts pages breaks when needed. + * It makes a rudimentary attempt to avoid "widows" and "orphans". + **/ + protected void paginate(View v, Rectangle2D allocation) { + // Figure out how tall this view is, and tell it to allocate + // that space among its children + double myheight = v.getPreferredSpan(View.Y_AXIS); + v.setSize((float)printWidth, (float)myheight); + + // Now loop through each of the children + int numkids = v.getViewCount(); + for(int i = 0; i < numkids; i++) { + View kid = v.getView(i); // this is the child we're working with + // Figure out its size and location + Shape kidshape = v.getChildAllocation(i, allocation); + if (kidshape == null) continue; + Rectangle2D kidbox = kidshape.getBounds2D(); + + // This is the Y coordinate of the bottom of the child + double kidpos = kidbox.getY() + kidbox.getHeight() - pageStart; + + // If this is the first child of a group, then we want to ensure + // that it doesn't get left by itself at the bottom of a page. + // I.e. we want to prevent "widows" + if ((numkids > 1) && (i == 0)) { + // If it is not near the end of the page, then just move + // on to the next child + if (kidpos < printY + printHeight*MARGIN_ADJUST) continue; + + // Otherwise, the child is near the bottom of the page, so + // break the page before this child and place this child on + // the new page. + breakPage(kidbox.getY()); + continue; + } + + // If this is the last child of a group, we don't want it to + // appear by itself at the top of a new page, so allow it to + // squeeze past the bottom margin if necessary. This helps to + // prevent "orphans" + if ((numkids > 1) && (i == numkids-1)) { + // If it fits normally, just move on to the next one + if (kidpos < printY + printHeight) continue; + + // Otherwise, if it fits with extra space, then break the + // at the end of the group + if (kidpos < printY + printHeight/MARGIN_ADJUST) { + breakPage(allocation.getY() + allocation.getHeight()); + continue; + } + } + + // If the child is not the first or last of a group, then we use + // the bottom margin strictly. If the child fits on the page, + // then move on to the next child. + if (kidpos < printY+printHeight) continue; + + // If we get here, the child doesn't fit on this page. If it has + // no children, then break the page before this child and continue. + if (kid.getViewCount() == 0) { + breakPage(kidbox.getY()); + continue; + } + + // If we get here, then the child did not fit on the page, but it + // has kids of its own, so recurse to see if any of those kids + // will fit on the page. + paginate(kid, kidbox); + } + } + + // For a document of n pages, this list stores the lengths of pages + // 0 through n-2. The last page is assumed to have a full length + ArrayList pageLengths = new ArrayList(); + + // For a document of n pages, this list stores the starting offset of + // pages 1 through n-1. The offset of page 0 is always 0 + ArrayList pageOffsets = new ArrayList(); + + /** + * Break a page at the specified Y coordinate. Store the necessary + * information into the pageLengths and pageOffsets lists + **/ + void breakPage(double y) { + double pageLength = y-pageStart-printY; + pageStart = y-printY; + pageLengths.add(new Double(pageLength)); + pageOffsets.add(new Double(pageStart)); + } + + /** Return the number of pages. This is a Pageable method. */ + public int getNumberOfPages() { return numPages; } + + /** + * Return the PageFormat object for the specified page. This + * implementation uses the computed length of the page in the returned + * PageFormat object. The PrinterJob will use this as a clipping region, + * which will prevent extraneous parts of the document from being drawn + * in the top and bottom margins. + **/ + public PageFormat getPageFormat(int pagenum) { + // On the last page, just return the user-specified page format + if (pagenum == numPages-1) return format; + + // Otherwise, look up the height of this page and return an + // appropriate PageFormat. + double pageLength = ((Double)pageLengths.get(pagenum)).doubleValue(); + PageFormat f = (PageFormat) format.clone(); + Paper p = f.getPaper(); + if (f.getOrientation() == PageFormat.PORTRAIT) + p.setImageableArea(printX, printY, printWidth, pageLength); + else + p.setImageableArea(printY, printX, pageLength, printWidth); + f.setPaper(p); + return f; + } + + /** + * This Printable method returns the Printable object for the specified + * page. Since this class implements both Pageable and Printable, it just + * returns this. + **/ + public Printable getPrintable(int pagenum) { return this; } + + /** + * This is the basic Printable method that prints a specified page + **/ + public int print(Graphics g, PageFormat format, int pageIndex) { + // Return an error code on attempts to print past the end of the doc + if (pageIndex >= numPages) return NO_SUCH_PAGE; + + // Cast the Graphics object so we can use Java2D operations + Graphics2D g2 = (Graphics2D)g; + + // Display a page number centered in the area of the top margin. + // Set a new clipping region so we can draw into the top margin + // But remember the original clipping region so we can restore it + Shape originalClip = g.getClip(); + g.setClip(new Rectangle(0, 0, (int)printWidth, (int)printY)); + // Compute the header to display, measure it, then display it + String numString = "- " + (pageIndex+1) + " -"; + Rectangle2D numBounds = // Get the width and height of the string + headerFont.getStringBounds(numString, g2.getFontRenderContext()); + LineMetrics metrics = // Get the ascent and descent of the font + headerFont.getLineMetrics(numString, g2.getFontRenderContext()); + g.setFont(headerFont); // Set the font + g.setColor(Color.black); // Print with black ink + g.drawString(numString, // Display the string + (int)(printX + (printWidth-numBounds.getWidth())/2), + (int)((printY-numBounds.getHeight())/2 + metrics.getAscent())); + g.setClip(originalClip); // Restore the clipping region + + // Figure out the staring position of the page within the document + double pageStart = 0.0; + if (pageIndex > 0) + pageStart = ((Double)pageOffsets.get(pageIndex-1)).doubleValue(); + + // Scroll so that the appropriate part of the document is lined up + // with the upper-left corner of the page + g2.translate(0.0, -pageStart); + + // Now paint the entire document. The PrinterJob will have + // established a clipping region, so that only the desired portion + // of the document will actually be drawn on this sheet of paper. + root.paint(g, drawRect); + + // Finally return a success code + return PAGE_EXISTS; + } + + /** + * This inner class is a concrete implementation of View, with a + * couple of key method implementations. An instance of this class + * is used as the parent of the root View object we want to print + **/ + static class ParentView extends View { + ViewFactory viewFactory; // The ViewFactory for the hierarchy of views + Container container; // The Container for the hierarchy of views + + public ParentView(View v, ViewFactory viewFactory, Container container) + { + super(v.getElement()); + this.viewFactory = viewFactory; + this.container = container; + } + + // These methods return key pieces of information required by + // the View hierarchy. + public ViewFactory getViewFactory() { return viewFactory; } + public Container getContainer() { return container; } + + // These methods are abstract in View, so we've got to provide + // dummy implementations of them here, even though they're never used. + public void paint(Graphics g, Shape allocation) {} + public float getPreferredSpan(int axis) { return 0.0f; } + public int viewToModel(float x,float y,Shape a,Position.Bias[] bias) { + return 0; + } + public Shape modelToView(int pos, Shape a, Position.Bias b) + throws BadLocationException { + return a; + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/PropertyTable.java b/src/main/java/com/davidflanagan/examples/gui/PropertyTable.java new file mode 100644 index 0000000..fa0c03c --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/PropertyTable.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import javax.swing.*; +import javax.swing.table.*; // TableModel and other JTable-related classes +import java.beans.*; // For JavaBean introspection +import java.util.*; // For array sorting + +/** + * This class is a JTable subclass that displays a table of the JavaBeans + * properties of any specified class. + **/ +public class PropertyTable extends JTable { + /** This main method allows the class to be demonstrated standalone */ + public static void main(String[] args) { + // Specify the name of the class as a command-line argument + Class beanClass = null; + try { + // Use reflection to get the Class from the classname + beanClass = Class.forName(args[0]); + } + catch (Exception e) { // Report errors + System.out.println("Can't find specified class: "+e.getMessage()); + System.out.println("Usage: java TableDemo "); + System.exit(0); + } + + // Create a table to display the properties of the specified class + JTable table = new PropertyTable(beanClass); + + // Then put the table in a scrolling window, put the scrolling + // window into a frame, and pop it all up on to the screen + JScrollPane scrollpane = new JScrollPane(table); + JFrame frame = new JFrame("Properties of JavaBean: " + args[0]); + frame.getContentPane().add(scrollpane); + frame.setSize(500, 400); + frame.setVisible(true); + } + + /** + * This constructor method specifies what data the table will display + * (the table model) and uses the TableColumnModel to customize the + * way that the table displays it. The hard work is done by the + * TableModel implementation below. + **/ + public PropertyTable(Class beanClass) { + // Set the data model for this table + try { + setModel(new JavaBeanPropertyTableModel(beanClass)); + } + catch (IntrospectionException e) { + System.err.println("WARNING: can't introspect: " + beanClass); + } + + // Tweak the appearance of the table by manipulating its column model + TableColumnModel colmodel = getColumnModel(); + + // Set column widths + colmodel.getColumn(0).setPreferredWidth(125); + colmodel.getColumn(1).setPreferredWidth(200); + colmodel.getColumn(2).setPreferredWidth(75); + colmodel.getColumn(3).setPreferredWidth(50); + + // Right justify the text in the first column + TableColumn namecol = colmodel.getColumn(0); + DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(); + renderer.setHorizontalAlignment(SwingConstants.RIGHT); + namecol.setCellRenderer(renderer); + } + + /** + * This class implements TableModel and represents JavaBeans property data + * in a way that the JTable component can display. If you've got some + * type of tabular data to display, implement a TableModel class to + * describe that data, and the JTable component will be able to display it. + **/ + static class JavaBeanPropertyTableModel extends AbstractTableModel { + PropertyDescriptor[] properties; // The properties to display + + /** + * The constructor: use the JavaBeans introspector mechanism to get + * information about all the properties of a bean. Once we've got + * this information, the other methods will interpret it for JTable. + **/ + public JavaBeanPropertyTableModel(Class beanClass) + throws IntrospectionException + { + // Use the introspector class to get "bean info" about the class. + BeanInfo beaninfo = Introspector.getBeanInfo(beanClass); + // Get the property descriptors from that BeanInfo class + properties = beaninfo.getPropertyDescriptors(); + // Now do a case-insensitive sort by property name + // The anonymous Comparator implementation specifies how to + // sort PropertyDescriptor objects by name + Arrays.sort(properties, new Comparator() { + public int compare(Object p, Object q) { + PropertyDescriptor a = (PropertyDescriptor) p; + PropertyDescriptor b = (PropertyDescriptor) q; + return a.getName().compareToIgnoreCase(b.getName()); + } + public boolean equals(Object o) { return o == this; } + }); + } + + // These are the names of the columns represented by this TableModel + static final String[] columnNames = new String[] { + "Name", "Type", "Access", "Bound" + }; + + // These are the types of the columns represented by this TableModel + static final Class[] columnTypes = new Class[] { + String.class, Class.class, String.class, Boolean.class + }; + + // These simple methods return basic information about the table + public int getColumnCount() { return columnNames.length; } + public int getRowCount() { return properties.length; } + public String getColumnName(int column) { return columnNames[column]; } + public Class getColumnClass(int column) { return columnTypes[column]; } + + /** + * This method returns the value that appears at the specified row and + * column of the table + **/ + public Object getValueAt(int row, int column) { + PropertyDescriptor prop = properties[row]; + switch(column) { + case 0: return prop.getName(); + case 1: return prop.getPropertyType(); + case 2: return getAccessType(prop); + case 3: return new Boolean(prop.isBound()); + default: return null; + } + } + + // A helper method called from getValueAt() above + String getAccessType(PropertyDescriptor prop) { + java.lang.reflect.Method reader = prop.getReadMethod(); + java.lang.reflect.Method writer = prop.getWriteMethod(); + if ((reader != null) && (writer != null)) return "Read/Write"; + else if (reader != null) return "Read-Only"; + else if (writer != null) return "Write-Only"; + else return "No Access"; // should never happen + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ResourceParser.java b/src/main/java/com/davidflanagan/examples/gui/ResourceParser.java new file mode 100644 index 0000000..6bd4ed1 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ResourceParser.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; + +/** + * This interface defines an extension mechanism that allows GUIResourceBundle + * to parse arbitrary resource types + **/ +public interface ResourceParser { + /** + * Return an array of classes that specify what kind of resources + * this parser can handle + **/ + public Class[] getResourceTypes(); + + /** + * Read the property named by key from the specified bundle, convert + * it to the specified type, and return it. For complex resources, + * the parser may need to read more than one property from the bundle; + * typically it may a number of properties whose names begin with the + * specified key. + **/ + public Object parse(GUIResourceBundle bundle, String key, Class type) + throws Exception; +} diff --git a/src/main/java/com/davidflanagan/examples/gui/Scribble.java b/src/main/java/com/davidflanagan/examples/gui/Scribble.java new file mode 100644 index 0000000..c983207 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/Scribble.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.border.*; + +/** + * This JFrame subclass is a simple "paint" application. + **/ +public class Scribble extends JFrame { + /** + * The main method instantiates an instance of the class, sets it size, + * and makes it visible on the screen + **/ + public static void main(String[] args) { + Scribble scribble = new Scribble(); + scribble.setSize(500, 300); + scribble.setVisible(true); + } + + // The scribble application relies on the ScribblePane2 component developed + // earlier. This field holds the ScribblePane2 instance it uses. + ScribblePane2 scribblePane; + + /** + * This constructor creates the GUI for this application. + **/ + public Scribble() { + super("Scribble"); // Call superclass constructor and set window title + + // Handle window close requests + this.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { System.exit(0); } + }); + + // All content of a JFrame (except for the menubar) goes in the + // Frame's internal "content pane", not in the frame itself. + // The same is true for JDialog and similar top-level containers. + Container contentPane = this.getContentPane(); + + // Specify a layout manager for the content pane + contentPane.setLayout(new BorderLayout()); + + // Create the main scribble pane component, give it a border, and + // a background color, and add it to the content pane + scribblePane = new ScribblePane2(); + scribblePane.setBorder(new BevelBorder(BevelBorder.LOWERED)); + scribblePane.setBackground(Color.white); + contentPane.add(scribblePane, BorderLayout.CENTER); + + // Create a menubar and add it to this window. Note that JFrame + // handles menus specially and has a special method for adding them + // outside of the content pane. + JMenuBar menubar = new JMenuBar(); // Create a menubar + this.setJMenuBar(menubar); // Display it in the JFrame + + // Create menus and add to the menubar + JMenu filemenu = new JMenu("File"); + JMenu colormenu = new JMenu("Color"); + menubar.add(filemenu); + menubar.add(colormenu); + + // Create some Action objects for use in the menus and toolbars. + // An Action combines a menu title and/or icon with an ActionListener. + // These Action classes are defined as inner classes below. + Action clear = new ClearAction(); + Action quit = new QuitAction(); + Action black = new ColorAction(Color.black); + Action red = new ColorAction(Color.red); + Action blue = new ColorAction(Color.blue); + Action select = new SelectColorAction(); + + // Populate the menus using Action objects + filemenu.add(clear); + filemenu.add(quit); + colormenu.add(black); + colormenu.add(red); + colormenu.add(blue); + colormenu.add(select); + + // Now create a toolbar, add actions to it, and add it to the + // top of the frame (where it appears underneath the menubar) + JToolBar toolbar = new JToolBar(); + toolbar.add(clear); + toolbar.add(select); + toolbar.add(quit); + contentPane.add(toolbar, BorderLayout.NORTH); + + // Create another toolbar for use as a color palette and add to + // the left side of the window. + JToolBar palette = new JToolBar(); + palette.add(black); + palette.add(red); + palette.add(blue); + palette.setOrientation(SwingConstants.VERTICAL); + contentPane.add(palette, BorderLayout.WEST); + } + + /** This inner class defines the "clear" action that clears the scribble */ + class ClearAction extends AbstractAction { + public ClearAction() { + super("Clear"); // Specify the name of the action + } + public void actionPerformed(ActionEvent e) { scribblePane.clear(); } + } + + /** This inner class defines the "quit" action to quit the program */ + class QuitAction extends AbstractAction { + public QuitAction() { super("Quit"); } + public void actionPerformed(ActionEvent e) { + // Use JOptionPane to confirm that the user really wants to quit + int response = + JOptionPane.showConfirmDialog(Scribble.this, "Really Quit?"); + if (response == JOptionPane.YES_OPTION) System.exit(0); + } + } + + /** + * This inner class defines an Action that sets the current drawing color + * of the ScribblePane2 component. Note that actions of this type have + * icons rather than labels + **/ + class ColorAction extends AbstractAction { + Color color; + public ColorAction(Color color) { + this.color = color; + putValue(Action.SMALL_ICON, new ColorIcon(color)); // specify icon + } + public void actionPerformed(ActionEvent e) { + scribblePane.setColor(color); // Set current drawing color + } + } + + /** + * This inner class implements Icon to draw a solid 16x16 block of the + * specified color. Most icons are instances of ImageIcon, but since + * we're only using solid colors here, it is easier to implement this + * custom Icon type + **/ + static class ColorIcon implements Icon { + Color color; + public ColorIcon(Color color) { this.color = color; } + // These two methods specify the size of the icon + public int getIconHeight() { return 16; } + public int getIconWidth() { return 16; } + // This method draws the icon + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(color); + g.fillRect(x, y, 16, 16); + } + } + + /** + * This inner class defines an Action that uses JColorChooser to allow + * the user to select a drawing color + **/ + class SelectColorAction extends AbstractAction { + public SelectColorAction() { super("Select Color..."); } + public void actionPerformed(ActionEvent e) { + Color color = JColorChooser.showDialog(Scribble.this, + "Select Drawing Color", + scribblePane.getColor()); + if (color != null) scribblePane.setColor(color); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ScribblePane1.java b/src/main/java/com/davidflanagan/examples/gui/ScribblePane1.java new file mode 100644 index 0000000..c134213 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ScribblePane1.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import javax.swing.*; // For JPanel component +import java.awt.*; // For Graphics object +import java.awt.event.*; // For Event and Listener objects + +/** + * A simple JPanel subclass that uses event listeners to allow the user + * to scribble with the mouse. Note that scribbles are not saved or redrawn. + **/ +public class ScribblePane1 extends JPanel + implements MouseListener, MouseMotionListener { + protected int last_x, last_y; // Previous mouse coordinates + + public ScribblePane1() { + // This component registers itself as an event listener for + // mouse events and mouse motion events. + this.addMouseListener(this); + this.addMouseMotionListener(this); + + // Give the component a preferred size + setPreferredSize(new Dimension(450,200)); + } + + // A method from the MouseListener interface. Invoked when the + // user presses a mouse button. + public void mousePressed(MouseEvent e) { + last_x = e.getX(); // remember the coordinates of the click + last_y = e.getY(); + } + + // A method from the MouseMotionListener interface. Invoked when the + // user drags the mouse with a button pressed. + public void mouseDragged(MouseEvent e) { + int x = e.getX(); // Get the current mouse position + int y = e.getY(); + // Draw a line from the saved coordinates to the current position + this.getGraphics().drawLine(last_x, last_y, x, y); + last_x = x; // Remember the current position + last_y = y; + } + + // The other, unused methods of the MouseListener interface. + public void mouseReleased(MouseEvent e) {} + public void mouseClicked(MouseEvent e) {} + public void mouseEntered(MouseEvent e) {} + public void mouseExited(MouseEvent e) {} + + // The other, unused, method of the MouseMotionListener interface. + public void mouseMoved(MouseEvent e) {} +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ScribblePane2.java b/src/main/java/com/davidflanagan/examples/gui/ScribblePane2.java new file mode 100644 index 0000000..ee77a72 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ScribblePane2.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import javax.swing.*; // For JPanel component +import java.awt.*; // For Graphics object +import java.awt.event.*; // For Event and Listener objects + +/** + * A simple JPanel subclass that uses event listeners to allow the user + * to scribble with the mouse. Note that scribbles are not saved or redrawn. + **/ +public class ScribblePane2 extends JPanel { + public ScribblePane2() { + // Give the component a preferred size + setPreferredSize(new Dimension(450,200)); + + // Register a mouse event handler defined as an inner class + // Note the call to requestFocus(). This is required in order for + // the component to receive key events. + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + moveto(e.getX(), e.getY()); // Move to click position + requestFocus(); // Take keyboard focus + } + }); + + // Register a mouse motion event handler defined as an inner class + // By subclassing MouseMotionAdapter rather than implementing + // MouseMotionListener, we only override the method we're interested + // in and inherit default (empty) implementations of the other methods. + addMouseMotionListener(new MouseMotionAdapter() { + public void mouseDragged(MouseEvent e) { + lineto(e.getX(), e.getY()); // Draw to mouse position + } + }); + + // Add a keyboard event handler to clear the screen on key 'C' + addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_C) clear(); + } + }); + } + + /** These are the coordinates of the the previous mouse position */ + protected int last_x, last_y; + + /** Remember the specified point */ + public void moveto(int x, int y) { + last_x = x; + last_y = y; + } + + /** Draw from the last point to this point, then remember new point */ + public void lineto(int x, int y) { + Graphics g = getGraphics(); // Get the object to draw with + g.setColor(color); // Tell it what color to use + g.drawLine(last_x, last_y, x, y); // Tell it what to draw + moveto(x, y); // Save the current point + } + + /** + * Clear the drawing area, using the component background color. This + * method works by requesting that the component be redrawn. Since this + * component does not have a paintComponent() method, nothing will be + * drawn. However, other parts of the component, such as borders or + * sub-components will be drawn correctly. + **/ + public void clear() { repaint(); } + + /** This field holds the current drawing color property */ + Color color = Color.black; + /** This is the property "setter" method for the color property */ + public void setColor(Color color) { this.color = color; } + /** This is the property "getter" method for the color property */ + public Color getColor() { return color; } + +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ScribblePane3.java b/src/main/java/com/davidflanagan/examples/gui/ScribblePane3.java new file mode 100644 index 0000000..0a88aa2 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ScribblePane3.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; // For Graphics object and colors +import javax.swing.*; // For JPanel component +import java.awt.event.*; // For ActionListener interface +import javax.swing.event.*; // For ListSelectionListener interface + +/** + * This scribble component includes a JButton to clear the screen, and + * a JList that lets the user select a drawing color. It uses + * event listener objects to handle events from those sub-components. + **/ +public class ScribblePane3 extends ScribblePane2 { + // These are colors the user can choose from + Color[] colors = new Color[] { Color.black, Color.red, Color.blue }; + // These are names for those colors + String[] colorNames = new String[] { "Black", "Red", "Blue" }; + + // Add JButton and JList components to the panel. + public ScribblePane3() { + // Implicit super() call here invokes the superclass constructor + + // Add a "Clear" button to the panel. + // Handle button events with an action listener + JButton clear = new JButton("Clear"); + clear.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { clear(); } + }); + this.add(clear); + + // Add a JList to allow color choices. + // Handle list selection events with a ListSelectionListener. + final JList colorList = new JList(colorNames); + colorList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + setColor(colors[colorList.getSelectedIndex()]); + } + }); + this.add(colorList); + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ScribblePane4.java b/src/main/java/com/davidflanagan/examples/gui/ScribblePane4.java new file mode 100644 index 0000000..8d51214 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ScribblePane4.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import javax.swing.*; // For JPanel component +import java.awt.*; // For Graphics object +import java.awt.event.*; // For Event and Listener objects + +/** + * Another scribble class. This one overrides the low-level event processing + * methods of the component instead of registering event listeners. + **/ +public class ScribblePane4 extends JPanel { + public ScribblePane4() { + // Give the component a preferred size + setPreferredSize(new Dimension(450,200)); + + // Tell the system what kind of events the component is interested in + enableEvents(AWTEvent.MOUSE_EVENT_MASK | + AWTEvent.MOUSE_MOTION_EVENT_MASK | + AWTEvent.KEY_EVENT_MASK); + } + + public void processMouseEvent(MouseEvent e) { + if (e.getID() == MouseEvent.MOUSE_PRESSED) { + moveto(e.getX(), e.getY()); + requestFocus(); + } + else super.processMouseEvent(e); // pass unhandled events to superclass + } + + public void processMouseMotionEvent(MouseEvent e) { + if (e.getID() == MouseEvent.MOUSE_DRAGGED) lineto(e.getX(), e.getY()); + else super.processMouseMotionEvent(e); + } + + public void processKeyEvent(KeyEvent e) { + if ((e.getID() == KeyEvent.KEY_PRESSED) && + (e.getKeyCode() == KeyEvent.VK_C)) clear(); + else super.processKeyEvent(e); // Give superclass a chance to handle + } + + /** These are the coordinates of the the previous mouse position */ + protected int last_x, last_y; + + /** Remember the specified point */ + public void moveto(int x, int y) { + last_x = x; + last_y = y; + } + + /** Draw from the last point to this point, then remember new point */ + public void lineto(int x, int y) { + getGraphics().drawLine(last_x, last_y, x, y); + moveto(x, y); + } + + /** Clear the drawing area, using the component background color */ + public void clear() { repaint(); } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ShowComponent.java b/src/main/java/com/davidflanagan/examples/gui/ShowComponent.java new file mode 100644 index 0000000..dcc9b13 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ShowComponent.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.beans.*; +import java.lang.reflect.*; +import java.util.Vector; + +/** + * This class is a program that uses reflection and JavaBeans introspection to + * create a set of named components, set named properties on those components, + * and display them. It allows the user to view the components using any + * installed look-and-feel. It is intended as a simple way to experiment with + * AWT and Swing components, and to view a number of the other examples + * developed in this chapter. It also demonstrates frames, menus, and the + * JTabbedPane component. + **/ +public class ShowComponent { + // The main program + public static void main(String[] args) { + // Process the command line to get the components to display + Vector components = getComponentsFromArgs(args); + + // Create a frame (a window) to display them in + JFrame frame = new JFrame("ShowComponent"); + + // Handle window close requests by exiting the VM + frame.addWindowListener(new WindowAdapter() { // Anonymous inner class + public void windowClosing(WindowEvent e) { System.exit(0); } + }); + + // Set up a menu system that allows the user to select the + // look-and-feel of the component from a list of installed PLAFs + JMenuBar menubar = new JMenuBar(); // Create a menubar + frame.setJMenuBar(menubar); // Tell the frame to display it + JMenu plafmenu = createPlafMenu(frame); // Create a menu + menubar.add(plafmenu); // Add the menu to the menubar + + // Create a JTabbedPane to display each of the components + JTabbedPane pane = new JTabbedPane(); + + // Now add each component as a tab of the tabbed pane + // Use the unqualified component classname as the tab text + for(int i = 0; i < components.size(); i++) { + Component c = (Component)components.elementAt(i); + String classname = c.getClass().getName(); + String tabname = classname.substring(classname.lastIndexOf('.')+1); + pane.addTab(tabname, c); + } + + // Add the tabbed pane to the frame. Note the call to getContentPane() + // This is required for JFrame, but not for most Swing components + frame.getContentPane().add(pane); + + // Set the frame size and pop it up + frame.pack(); // Make frame as big as its kids need + frame.setVisible(true); // Make the frame visible on the screen + + // The main() method exits now but the Java VM keeps running because + // all AWT programs automatically start an event-handling thread. + } + + /** + * This static method queries the system to find out what Pluggable + * Look-and-Feel (PLAF) implementations are available. Then it creates a + * JMenu component that lists each of the implementations by name and + * allows the user to select one of them using JRadioButtonMenuItem + * components. When the user selects one, the selected menu item + * traverses the component hierarchy and tells all components to use the + * new PLAF. + **/ + public static JMenu createPlafMenu(final JFrame frame) { + // Create the menu + JMenu plafmenu = new JMenu("Look and Feel"); + + // Create an object used for radio button mutual exclusion + ButtonGroup radiogroup = new ButtonGroup(); + + // Look up the available look and feels + UIManager.LookAndFeelInfo[] plafs = + UIManager.getInstalledLookAndFeels(); + + // Loop through the plafs, and add a menu item for each one + for(int i = 0; i < plafs.length; i++) { + String plafName = plafs[i].getName(); + final String plafClassName = plafs[i].getClassName(); + + // Create the menu item + JMenuItem item = plafmenu.add(new JRadioButtonMenuItem(plafName)); + + // Tell the menu item what to do when it is selected + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + try { + // Set the new look and feel + UIManager.setLookAndFeel(plafClassName); + // Tell each component to change its look-and-feel + SwingUtilities.updateComponentTreeUI(frame); + // Tell the frame to resize itself to the its + // children's new desired sizes + frame.pack(); + } + catch(Exception ex) { System.err.println(ex); } + } + + }); + + // Only allow one menu item to be selected at once + radiogroup.add(item); + } + return plafmenu; + } + + /** + * This method loops through the command line arguments looking for + * class names of components to create and property settings for those + * components in the form name=value. This method demonstrates + * reflection and JavaBeans introspection as they can be applied to + * dynamically created GUIs + **/ + public static Vector getComponentsFromArgs(String[] args) { + Vector components = new Vector(); // List of components to return + Component component = null; // The current component + PropertyDescriptor[] properties = null; // Properties of the component + Object[] methodArgs = new Object[1]; // We'll use this below + + nextarg: // This is a labeled loop + for(int i = 0; i < args.length; i++) { // Loop through all arguments + // If the argument does not contain an equal sign, then it is + // a component class name. Otherwise it is a property setting + int equalsPos = args[i].indexOf('='); + if (equalsPos == -1) { // Its the name of a component + try { + // Load the named component class + Class componentClass = Class.forName(args[i]); + // Instantiate it to create the component instance + component = (Component)componentClass.newInstance(); + // Use JavaBeans to introspect the component + // And get the list of properties it supports + BeanInfo componentBeanInfo = + Introspector.getBeanInfo(componentClass); + properties = componentBeanInfo.getPropertyDescriptors(); + } + catch(Exception e) { + // If any step failed, print an error and exit + System.out.println("Can't load, instantiate, " + + "or introspect: " + args[i]); + System.exit(1); + } + + // If we succeeded, store the component in the vector + components.addElement(component); + } + else { // The arg is a name=value property specification + String name =args[i].substring(0, equalsPos); // property name + String value =args[i].substring(equalsPos+1); // property value + + // If we don't have a component to set this property on, skip! + if (component == null) continue nextarg; + + // Now look through the properties descriptors for this + // component to find one with the same name. + for(int p = 0; p < properties.length; p++) { + if (properties[p].getName().equals(name)) { + // Okay, we found a property of the right name. + // Now get its type, and the setter method + Class type = properties[p].getPropertyType(); + Method setter = properties[p].getWriteMethod(); + + // Check if property is read-only! + if (setter == null) { + System.err.println("Property " + name+ + " is read-only"); + continue nextarg; // continue with next argument + } + + // Try to convert the property value to the right type + // We support a small set of common property types here + // Store the converted value in an Object[] so it can + // be easily passed when we invoke the property setter + try { + if (type == String.class) { // no conversion needed + methodArgs[0] = value; + } + else if (type == int.class) { // String to int + methodArgs[0] = Integer.valueOf(value); + } + else if (type == boolean.class) { // to boolean + methodArgs[0] = Boolean.valueOf(value); + } + else if (type == Color.class) { // to Color + methodArgs[0] = Color.decode(value); + } + else if (type == Font.class) { // String to Font + methodArgs[0] = Font.decode(value); + } + else { + // If we can't convert, ignore the property + System.err.println("Property " + name + + " is of unsupported type " + + type.getName()); + continue nextarg; + } + } + catch (Exception e) { + // If conversion failed, continue with the next arg + System.err.println("Can't convert '" + value + + "' to type " + type.getName() + + " for property " + name); + continue nextarg; + } + + // Finally, use reflection to invoke the property + // setter method of the component we created, and pass + // in the converted property value. + try { setter.invoke(component, methodArgs); } + catch (Exception e) { + System.err.println("Can't set property: " + name); + } + + // Now go on to next command-line arg + continue nextarg; + } + } + + // If we get here, we didn't find the named property + System.err.println("Warning: No such property: " + name); + } + } + + return components; + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ThemeManager.java b/src/main/java/com/davidflanagan/examples/gui/ThemeManager.java new file mode 100644 index 0000000..aea333e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ThemeManager.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.plaf.*; +import javax.swing.plaf.metal.MetalLookAndFeel; +import javax.swing.plaf.metal.DefaultMetalTheme; + +/** + * This class reads theme descriptions from a GUIResourceBundle and uses them + * to specify colors and fonts for the Metal look-and-feel. + **/ +public class ThemeManager { + JFrame frame; // The frame which themes are applied to + GUIResourceBundle resources; // Properties describing the themes + + /** + * Build a ThemeManager for the frame and resource bundle. If there + * is a default theme specified, apply it to the frame + **/ + public ThemeManager(JFrame frame, GUIResourceBundle resources) { + this.frame = frame; + this.resources = resources; + String defaultName = getDefaultThemeName(); + if (defaultName != null) setTheme(defaultName); + } + + /** Look up the named theme, and apply it to the frame */ + public void setTheme(String themeName) { + // Look up the theme in the resource bundle + Theme theme = new Theme(resources, themeName); + // Make it the current theme + MetalLookAndFeel.setCurrentTheme(theme); + // Re-apply the Metal look-and-feel to install new theme + try { UIManager.setLookAndFeel(new MetalLookAndFeel()); } + catch(UnsupportedLookAndFeelException e) {} + // Propagate the new l&f across the entire component tree of the frame + SwingUtilities.updateComponentTreeUI(frame); + } + + /** Get the "display name" or label of the named theme */ + public String getDisplayName(String themeName) { + return resources.getString(themeName + ".name", null); + } + + /** Get the name of the default theme, or null */ + public String getDefaultThemeName() { + return resources.getString("defaultTheme", null); + } + + /** + * Get the list of all known theme names. The returned values are + * theme property names, not theme display names. + **/ + public String[] getAllThemeNames() { + java.util.List names = resources.getStringList("themelist"); + return (String[]) names.toArray(new String[names.size()]); + } + + /** + * Get a JMenu that lists all known themes by display name and + * installs any selected theme. + **/ + public JMenu getThemeMenu() { + String[] names = getAllThemeNames(); + String defaultName = getDefaultThemeName(); + JMenu menu = new JMenu("Themes"); + ButtonGroup buttongroup = new ButtonGroup(); + for(int i = 0; i < names.length; i++) { + final String themeName = names[i]; + String displayName = getDisplayName(themeName); + JMenuItem item = menu.add(new JRadioButtonMenuItem(displayName)); + buttongroup.add(item); + if (themeName.equals(defaultName)) item.setSelected(true); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + setTheme(themeName); + } + }); + } + return menu; + } + + /** + * This class extends the DefaultMetalTheme class to return Color and + * Font values read from a GUIResourceBundle + **/ + public static class Theme extends DefaultMetalTheme { + // These fields are the values returned by this Theme + String displayName; + FontUIResource controlFont, menuFont, smallFont; + FontUIResource systemFont, userFont, titleFont; + ColorUIResource primary1, primary2, primary3; + ColorUIResource secondary1, secondary2, secondary3; + + /** + * This constructor reads all the values it needs from the + * GUIResourceBundle. It uses intelligent defaults if properties + * are not specified. + **/ + public Theme(GUIResourceBundle resources, String name) { + // Use this theme object to get default font values from + DefaultMetalTheme defaultTheme = new DefaultMetalTheme(); + + // Look up the display name of the theme + displayName = resources.getString(name + ".name", null); + + // Look up the fonts for the theme + Font control = resources.getFont(name + ".controlFont", null); + Font menu = resources.getFont(name + ".menuFont", null); + Font small = resources.getFont(name + ".smallFont", null); + Font system = resources.getFont(name + ".systemFont", null); + Font user = resources.getFont(name + ".userFont", null); + Font title = resources.getFont(name + ".titleFont", null); + + // Convert fonts to FontUIResource, or get defaults + if (control != null) controlFont = new FontUIResource(control); + else controlFont = defaultTheme.getControlTextFont(); + if (menu != null) menuFont = new FontUIResource(menu); + else menuFont = defaultTheme.getMenuTextFont(); + if (small != null) smallFont = new FontUIResource(small); + else smallFont = defaultTheme.getSubTextFont(); + if (system != null) systemFont = new FontUIResource(system); + else systemFont = defaultTheme.getSystemTextFont(); + if (user != null) userFont = new FontUIResource(user); + else userFont = defaultTheme.getUserTextFont(); + if (title != null) titleFont = new FontUIResource(title); + else titleFont = defaultTheme.getWindowTitleFont(); + + // Look up primary and secondary colors + Color primary = resources.getColor(name + ".primary", null); + Color secondary = resources.getColor(name + ".secondary", null); + + // Derive all six colors from these two, using defaults if needed + if (primary != null) primary1 = new ColorUIResource(primary); + else primary1 = new ColorUIResource(102, 102, 153); + primary2 = new ColorUIResource(primary1.brighter()); + primary3 = new ColorUIResource(primary2.brighter()); + if (secondary != null) secondary1 = new ColorUIResource(secondary); + else secondary1 = new ColorUIResource(102, 102, 102); + secondary2 = new ColorUIResource(secondary1.brighter()); + secondary3 = new ColorUIResource(secondary2.brighter()); + } + + // These methods override DefaultMetalTheme and return the property + // values we looked up and computed for this theme + public String getName() { return displayName; } + public FontUIResource getControlTextFont() { return controlFont;} + public FontUIResource getSystemTextFont() { return systemFont;} + public FontUIResource getUserTextFont() { return userFont;} + public FontUIResource getMenuTextFont() { return menuFont;} + public FontUIResource getWindowTitleFont() { return titleFont;} + public FontUIResource getSubTextFont() { return smallFont;} + protected ColorUIResource getPrimary1() { return primary1; } + protected ColorUIResource getPrimary2() { return primary2; } + protected ColorUIResource getPrimary3() { return primary3; } + protected ColorUIResource getSecondary1() { return secondary1; } + protected ColorUIResource getSecondary2() { return secondary2; } + protected ColorUIResource getSecondary3() { return secondary3; } + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/ToolBarParser.java b/src/main/java/com/davidflanagan/examples/gui/ToolBarParser.java new file mode 100644 index 0000000..7ee333c --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/ToolBarParser.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import javax.swing.*; +import java.util.*; + +/** + * Parse a JToolBar from a ResourceBundle. A toolbar is represented + * simply as a list of action property names. E.g.: + * toolbar: action.save, action.print, action.quit + **/ +public class ToolBarParser implements ResourceParser { + static final Class[] supportedTypes = new Class[] { JToolBar.class }; + public Class[] getResourceTypes() { return supportedTypes; } + + public Object parse(GUIResourceBundle bundle, String key, Class type) + throws MissingResourceException + { + // Get the value of the key as a list of strings + List toolList = bundle.getStringList(key); + + // Create a ToolBar + JToolBar toolbar = new JToolBar(); + + // Create a JTool for each of the tool property names, + // and add it to the bar + int numtools = toolList.size(); + for(int i = 0; i < numtools; i++) { + // Get the action name + String tool = (String)toolList.get(i); + // Get the Action object associated with that name + Action action = (Action) bundle.getResource(tool, Action.class); + // Add the action to the toolbar, and get the JButton it creates + JButton button = toolbar.add(action); + // If the action contains a description, use it as the tooltip + String tooltip = (String)action.getValue(Action.SHORT_DESCRIPTION); + if (tooltip != null) button.setToolTipText(tooltip); + } + + return toolbar; + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/WebBrowser.java b/src/main/java/com/davidflanagan/examples/gui/WebBrowser.java new file mode 100644 index 0000000..cde73f7 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/WebBrowser.java @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.gui; +import java.awt.*; // LayoutManager stuff +import javax.swing.*; // Swing components +import java.awt.event.*; // AWT event handlers +import javax.swing.event.*; // Swing event handlers +import java.beans.*; // JavaBeans event handlers +import java.awt.print.*; // Printing functionality +import java.io.*; // Input/output +import java.net.*; // Networking with URLs +import java.util.*; // Hashtables and other utilities +// Import this class by name. JFileChooser uses it, and its name conflicts +// with java.io.FileFilter +import javax.swing.filechooser.FileFilter; +// Import a class for printing Swing documents. See printing chapter. +import com.davidflanagan.examples.print.PrintableDocument; + +/** + * This class implements a simple web browser using the HTML + * display capabilities of the JEditorPane component. + **/ +public class WebBrowser extends JFrame + implements HyperlinkListener, PropertyChangeListener +{ + /** + * A simple main() method that allows the WebBrowser class to be used + * as a stand-alone application. + **/ + public static void main(String[] args) throws IOException { + // End the program when there are no more open browser windows + WebBrowser.setExitWhenLastWindowClosed(true); + WebBrowser browser = new WebBrowser(); // Create a browser window + browser.setSize(800, 600); // Set its size + browser.setVisible(true); // Make it visible. + + // Tell the browser what to display. This method is defined below. + browser.displayPage((args.length > 0) ? args[0] : browser.getHome()); + } + + // This class uses GUIResourceBundle to create its menubar and toolbar + // This static initializer performs one-time registration of the + // required ResourceParser classes. + static { + GUIResourceBundle.registerResourceParser(new MenuBarParser()); + GUIResourceBundle.registerResourceParser(new MenuParser()); + GUIResourceBundle.registerResourceParser(new ActionParser()); + GUIResourceBundle.registerResourceParser(new CommandParser()); + GUIResourceBundle.registerResourceParser(new ToolBarParser()); + } + + // These are the Swing components that the browser uses + JEditorPane textPane; // Where the HTML is displayed + JLabel messageLine; // Displays one-line messages + JTextField urlField; // Displays and edits the current URL + JFileChooser fileChooser; // Allows the user to select a local file + + // These are Actions that are used in the menubar and toolbar. + // We obtain explicit references to them from the GUIResourceBundle + // so we can enable and disable them. + Action backAction, forwardAction; + + // These fields are used to maintain the browsing history of the window + java.util.List history = new ArrayList(); // The history list + int currentHistoryPage = -1; // Current location in it + public static final int MAX_HISTORY = 50; // Trim list when over this size + + // These static fields control the behavior of the close() action + static int numBrowserWindows = 0; + static boolean exitWhenLastWindowClosed = false; + + // This is where the "home()" method takes us. See also setHome() + String home = "http://www.davidflanagan.com"; // A default value + + /** Create and initialize a new WebBrowser window */ + public WebBrowser() { + super(); // Chain to JFrame constructor + + textPane = new JEditorPane(); // Create HTML window + textPane.setEditable(false); // Don't allow the user to edit it + + // Register action listeners. The first is to handle hyperlinks. + // The second is to receive property change notifications, which tell + // us when a document is done loading. This class implements these + // EventListener interfaces, and the methods are defined below + textPane.addHyperlinkListener(this); + textPane.addPropertyChangeListener(this); + + // Put the text pane in a JScrollPane in the center of the window + this.getContentPane().add(new JScrollPane(textPane), + BorderLayout.CENTER); + + // Now create a message line and place it at the bottom of the window + messageLine = new JLabel(" "); + this.getContentPane().add(messageLine, BorderLayout.SOUTH); + + // Read the file WebBrowserResources.properties (and any localized + // variants appropriate for the current Locale) to create a + // GUIResourceBundle from which we'll get our menubar and toolbar. + GUIResourceBundle resources = + new GUIResourceBundle(this,"com.davidflanagan.examples.gui." + + "WebBrowserResources"); + + // Read a menubar from the resource bundle and display it + JMenuBar menubar = (JMenuBar) resources.getResource("menubar", + JMenuBar.class); + this.setJMenuBar(menubar); + + // Read a toolbar from the resource bundle. Don't display it yet. + JToolBar toolbar = + (JToolBar) resources.getResource("toolbar", JToolBar.class); + + // Create a text field that the user can enter a URL in. + // Set up an action listener to respond to the ENTER key in that field + urlField = new JTextField(); + urlField.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + displayPage(urlField.getText()); + } + }); + + // Add the URL field and a label for it to the end of the toolbar + toolbar.add(new JLabel(" URL:")); + toolbar.add(urlField); + + // And add the toolbar to the top of the window + this.getContentPane().add(toolbar, BorderLayout.NORTH); + + // Read cached copies of two Action objects from the resource bundle + // These actions are used by the menubar and toolbar, and enabling and + // disabling them enables and disables the menu and toolbar items. + backAction = (Action)resources.getResource("action.back",Action.class); + forwardAction = + (Action)resources.getResource("action.forward", Action.class); + + // Start off with both actions disabled + backAction.setEnabled(false); + forwardAction.setEnabled(false); + + // Create a ThemeManager for this frame, + // and add a Theme menu to the menubar + ThemeManager themes = new ThemeManager(this, resources); + menubar.add(themes.getThemeMenu()); + + // Keep track of how many web browser windows are open + WebBrowser.numBrowserWindows++; + } + + /** Set the static property that controls the behavior of close() */ + public static void setExitWhenLastWindowClosed(boolean b) { + exitWhenLastWindowClosed = b; + } + + /** These are accessor methods for the home property. */ + public void setHome(String home) { this.home = home; } + public String getHome() { return home; } + + /** + * This internal method attempts to load and display the specified URL. + * It is called from various places throughout the class. + **/ + boolean visit(URL url) { + try { + String href = url.toString(); + // Start animating. Animation is stopped in propertyChanged() + startAnimation("Loading " + href + "..."); + textPane.setPage(url); // Load and display the URL + this.setTitle(href); // Display URL in window titlebar + urlField.setText(href); // Display URL in text input field + return true; // Return success + } + catch (IOException ex) { // If page loading fails + stopAnimation(); + messageLine.setText("Can't load page: " + ex.getMessage()); + return false; // Return failure + } + } + + /** + * Ask the browser to display the specified URL, and put it in the + * history list. + **/ + public void displayPage(URL url) { + if (visit(url)) { // go to the specified url, and if we succeed: + history.add(url); // Add the url to the history list + int numentries = history.size(); + if (numentries > MAX_HISTORY+10) { // Trim history when too large + history = history.subList(numentries-MAX_HISTORY, numentries); + numentries = MAX_HISTORY; + } + currentHistoryPage = numentries-1; // Set current history page + // If we can go back, then enable the Back action + if (currentHistoryPage > 0) backAction.setEnabled(true); + } + } + + /** Like displayPage(URL), but takes a string instead */ + public void displayPage(String href) { + try { + displayPage(new URL(href)); + } + catch (MalformedURLException ex) { + messageLine.setText("Bad URL: " + href); + } + } + + /** Allow the user to choose a local file, and display it */ + public void openPage() { + // Lazy creation: don't create the JFileChooser until it is needed + if (fileChooser == null) { + fileChooser = new JFileChooser(); + // This javax.swing.filechooser.FileFilter displays only HTML files + FileFilter filter = new FileFilter() { + public boolean accept(File f) { + String fn = f.getName(); + if (fn.endsWith(".html") || fn.endsWith(".htm")) + return true; + else return false; + } + public String getDescription() { return "HTML Files"; } + }; + fileChooser.setFileFilter(filter); + fileChooser.addChoosableFileFilter(filter); + } + + // Ask the user to choose a file. + int result = fileChooser.showOpenDialog(this); + if (result == JFileChooser.APPROVE_OPTION) { + // If they didn't click "Cancel", then try to display the file. + File selectedFile = fileChooser.getSelectedFile(); + String url = "file://" + selectedFile.getAbsolutePath(); + displayPage(url); + } + } + + /** Go back to the previously displayed page. */ + public void back() { + if (currentHistoryPage > 0) // go back, if we can + visit((URL)history.get(--currentHistoryPage)); + // Enable or disable actions as appropriate + backAction.setEnabled((currentHistoryPage > 0)); + forwardAction.setEnabled((currentHistoryPage < history.size()-1)); + } + + /** Go forward to the next page in the history list */ + public void forward() { + if (currentHistoryPage < history.size()-1) // go forward, if we can + visit((URL)history.get(++currentHistoryPage)); + // Enable or disable actions as appropriate + backAction.setEnabled((currentHistoryPage > 0)); + forwardAction.setEnabled((currentHistoryPage < history.size()-1)); + } + + /** Reload the current page in the history list */ + public void reload() { + if (currentHistoryPage != -1) + visit((URL)history.get(currentHistoryPage)); + } + + /** Display the page specified by the "home" property */ + public void home() { displayPage(getHome()); } + + /** Open a new browser window */ + public void newBrowser() { + WebBrowser b = new WebBrowser(); + b.setSize(this.getWidth(), this.getHeight()); + b.setVisible(true); + } + + /** + * Close this browser window. If this was the only open window, + * and exitWhenLastBrowserClosed is true, then exit the VM + **/ + public void close() { + this.setVisible(false); // Hide the window + this.dispose(); // Destroy the window + synchronized(WebBrowser.class) { // Synchronize for thread-safety + WebBrowser.numBrowserWindows--; // There is one window fewer now + if ((numBrowserWindows==0) && exitWhenLastWindowClosed) + System.exit(0); // Exit if it was the last one + } + } + + /** + * Exit the VM. If confirm is true, ask the user if they are sure. + * Note that showConfirmDialog() displays a dialog, waits for the user, + * and returns the user's response (i.e. the button the user selected). + **/ + public void exit(boolean confirm) { + if (!confirm || + (JOptionPane.showConfirmDialog(this, // dialog parent + /* message to display */ "Are you sure you want to quit?", + /* dialog title */ "Really Quit?", + /* dialog buttons */ JOptionPane.YES_NO_OPTION) == + JOptionPane.YES_OPTION)) // If Yes button was clicked + System.exit(0); + } + + /** + * Print the contents of the text pane using the java.awt.print API + * Note that this API does not work efficiently in Java 1.2 + * All the hard work is done by the PrintableDocument class. + **/ + public void print() { + // Get a PrinterJob object from the system + PrinterJob job = PrinterJob.getPrinterJob(); + // This is the object that we are going to print + PrintableDocument pd = new PrintableDocument(textPane); + // Tell the PrinterJob what we want to print + job.setPageable(pd); + // Display a print dialog, asking the user what pages to print, what + // printer to print to, and giving the user a chance to cancel. + if (job.printDialog()) { // If the user did not cancel + try { job.print(); } // Start printing! + catch(PrinterException ex) { // display errors nicely + messageLine.setText("Couldn't print: " + ex.getMessage()); + } + } + } + + /** + * This method implements HyperlinkListener. It is invoked when the user + * clicks on a hyperlink, or move the mouse onto or off of a link + **/ + public void hyperlinkUpdate(HyperlinkEvent e) { + HyperlinkEvent.EventType type = e.getEventType(); // what happened? + if (type == HyperlinkEvent.EventType.ACTIVATED) { // Click! + displayPage(e.getURL()); // Follow the link; display new page + } + else if (type == HyperlinkEvent.EventType.ENTERED) { // Mouse over! + // When mouse goes over a link, display it in the message line + messageLine.setText(e.getURL().toString()); + } + else if (type == HyperlinkEvent.EventType.EXITED) { // Mouse out! + messageLine.setText(" "); // Clear the message line + } + } + + /** + * This method implements java.beans.PropertyChangeListener. It is + * invoked whenever a bound property changes in the JEditorPane object. + * The property we are interested in is the "page" property, because it + * tells us when a page has finished loading. + **/ + public void propertyChange(PropertyChangeEvent e) { + if (e.getPropertyName().equals("page")) // If the page property changed + stopAnimation(); // Then stop the loading... animation + } + + /** + * The fields and methods below implement a simple animation in the + * web browser message line; they are used to provide user feedback + * while web pages are loading. + **/ + String animationMessage; // The "loading..." message to display + int animationFrame = 0; // What "frame" of the animation are we on + String[] animationFrames = new String[] { // The content of each "frame" + "-", "\\", "|", "/", "-", "\\", "|", "/", + ",", ".", "o", "0", "O", "#", "*", "+" + }; + + /** This object calls the animate() method 8 times a second */ + javax.swing.Timer animator = + new javax.swing.Timer(125, new ActionListener() { + public void actionPerformed(ActionEvent e) { animate(); } + }); + + /** Display the next frame. Called by the animator timer */ + void animate() { + String frame = animationFrames[animationFrame++]; // Get next frame + messageLine.setText(animationMessage + " " + frame); // Update msgline + animationFrame = animationFrame % animationFrames.length; + } + + /** Start the animation. Called by the visit() method. */ + void startAnimation(String msg) { + animationMessage = msg; // Save the message to display + animationFrame = 0; // Start with frame 0 of the animation + animator.start(); // Tell the timer to start firing. + } + + /** Stop the animation. Called by propertyChanged() method. */ + void stopAnimation() { + animator.stop(); // Tell the timer to stop firing events + messageLine.setText(" "); // Clear the message line + } +} diff --git a/src/main/java/com/davidflanagan/examples/gui/WebBrowserResources.properties b/src/main/java/com/davidflanagan/examples/gui/WebBrowserResources.properties new file mode 100644 index 0000000..d370992 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/gui/WebBrowserResources.properties @@ -0,0 +1,70 @@ +menubar: menu.file menu.go +menu.file: File: action.new action.open action.print - action.close action.exit +menu.go: Go: action.back action.forward action.reload action.home + +toolbar: action.back action.forward action.reload action.home action.oreilly + +action.back: back(); +action.back.label: Back +action.back.description: View previous page in history + +action.forward: forward(); +action.forward.label: Forward +action.forward.description: View next page in history + +action.reload: reload(); +action.reload.label: Reload +action.reload.description: Reload the current page + +action.home: home(); +action.home.label: Home +action.home.description: Go to home page + +action.oreilly: displayPage("http://www.oreilly.com"); +action.oreilly.label: O'Reilly +action.oreilly.description: O'Reilly & Associates home page + +action.new: newBrowser(); +action.new.label: New Web Browser +action.new.description: Create a new Web Browser window + +action.open: openPage(); +action.open.label: Open... +action.open.description: Display a local HTML file + +action.print: print(); +action.print.label: Print... +action.print.description: Print the current page + +action.close: close(); +action.close.label: Close +action.close.description: Close this window + +action.exit: exit(true); +action.exit.label: Exit +action.exit.description: Close all windows and exit + +themelist: theme.metal theme.rose, theme.lime, theme.primary, theme.bigfont +defaultTheme: theme.metal + +theme.metal.name: Default Metal + +theme.rose.name: Rose +theme.rose.primary: #905050 +theme.rose.secondary: #906050 + +theme.lime.name: Lime +theme.lime.primary: #509050 +theme.lime.secondary: #506060 + +theme.primary.name: Primary Colors +theme.primary.primary: #202090 +theme.primary.secondary: #209020 + +theme.bigfont.name: Big Fonts +theme.bigfont.controlFont: sanserif-bold-18 +theme.bigfont.menuFont: sanserif-bold-18 +theme.bigfont.smallFont: sansserif-plain-14 +theme.bigfont.systemFont: sanserif-plain-14 +theme.bigfont.userFont: sanserif-plain-14 +theme.bigfont.titleFont: sansserif-bold-18 diff --git a/src/main/java/com/davidflanagan/examples/i18n/ConvertEncoding.java b/src/main/java/com/davidflanagan/examples/i18n/ConvertEncoding.java new file mode 100644 index 0000000..3f94019 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/i18n/ConvertEncoding.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.i18n; +import java.io.*; + +/** A program to convert from one character encoding to another */ +public class ConvertEncoding { + public static void main(String[] args) { + String from = null, to = null; + String infile = null, outfile = null; + for(int i = 0; i < args.length; i++) { // Parse command-line arguments. + if (i == args.length-1) usage(); // All args require another. + if (args[i].equals("-from")) from = args[++i]; + else if (args[i].equals("-to")) to = args[++i]; + else if (args[i].equals("-in")) infile = args[++i]; + else if (args[i].equals("-out")) outfile = args[++i]; + else usage(); + } + + try { convert(infile, outfile, from, to); } // Attempt conversion. + catch (Exception e) { // Handle exceptions. + LocalizedError.display(e); // Defined at the end of this chapter. + System.exit(1); + } + } + + public static void usage() { + System.err.println("Usage: java ConvertEncoding \n" + + "Options:\n\t-from \n\t" + + "-to \n\t" + + "-in \n\t-out "); + System.exit(1); + } + + public static void convert(String infile, String outfile, + String from, String to) + throws IOException, UnsupportedEncodingException + { + // Set up byte streams. + InputStream in; + if (infile != null) in = new FileInputStream(infile); + else in = System.in; + OutputStream out; + if (outfile != null) out = new FileOutputStream(outfile); + else out = System.out; + + // Use default encoding if no encoding is specified. + if (from == null) from = System.getProperty("file.encoding"); + if (to == null) to = System.getProperty("file.encoding"); + + // Set up character streams. + Reader r = new BufferedReader(new InputStreamReader(in, from)); + Writer w = new BufferedWriter(new OutputStreamWriter(out, to)); + + // Copy characters from input to output. The InputStreamReader + // converts from the input encoding to Unicode, and the + // OutputStreamWriter converts from Unicode to the output encoding. + // Characters that cannot be represented in the output encoding are + // output as '?' + char[] buffer = new char[4096]; + int len; + while((len = r.read(buffer)) != -1) // Read a block of input. + w.write(buffer, 0, len); // And write it out. + r.close(); // Close the input. + w.close(); // Flush and close output. + } +} diff --git a/src/main/java/com/davidflanagan/examples/i18n/Errors.properties b/src/main/java/com/davidflanagan/examples/i18n/Errors.properties new file mode 100644 index 0000000..efcb6eb --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/i18n/Errors.properties @@ -0,0 +1,22 @@ +# +# This is the file Errors.properties +# One property for each class of exceptions that our program might +# report. Note the use of backslashes to continue long lines onto the +# next. Also note the use of \n and \t for newlines and tabs +# +java.io.FileNotFoundException: \ +Error: File "{0}" not found\n\t\ +Error occurred at line {3} of file "{2}"\n\tat {4} + +java.io.UnsupportedEncodingException: \ +Error: Specified encoding not supported\n\t\ +Error occurred at line {3} of file "{2}"\n\tat {4,time} on {4,date} + +java.io.CharConversionException:\ +Error: Character conversion failure. Input data is not in specified format. + +# A generic resource. Display a message for any error or exception that +# is not handled by a more specific resource. +java.lang.Throwable:\ +Error: {1}: {0}\n\t\ +Error occurred at line {3} of file "{2}"\n\t{4,time,long} {4,date,long} diff --git a/src/main/java/com/davidflanagan/examples/i18n/LocalizedError.java b/src/main/java/com/davidflanagan/examples/i18n/LocalizedError.java new file mode 100644 index 0000000..23c5674 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/i18n/LocalizedError.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.i18n; +import java.text.*; +import java.io.*; +import java.util.*; + +/** + * A convenience class that can display a localized exception message + * depending on the class of the exception. It uses a MessageFormat, + * and passes five arguments that the localized message may include: + * {0}: the message included in the exception or error. + * {1}: the full class name of the exception or error. + * {2}: a guess at what file the exception was caused by. + * {3}: a line number in that file. + * {4}: the current date and time. + * Messages are looked up in a ResourceBundle with the basename + * "Errors", using a the full class name of the exception object as + * the resource name. If no resource is found for a given exception + * class, the superclasses are checked. + **/ +public class LocalizedError { + public static void display(Throwable error) { + ResourceBundle bundle; + // Try to get the resource bundle. + // If none, print the error in a non-localized way. + try { bundle = ResourceBundle.getBundle("Errors"); } + catch (MissingResourceException e) { + error.printStackTrace(System.err); + return; + } + + // Look up a localized message resource in that bundle, using the + // classname of the error (or its superclasses) as the resource name. + // If no resource was found, display the error without localization. + String message = null; + Class c = error.getClass(); + while((message == null) && (c != Object.class)) { + try { message = bundle.getString(c.getName()); } + catch (MissingResourceException e) { c = c.getSuperclass(); } + } + if (message == null) { error.printStackTrace(System.err); return; } + + // Try to figure out the filename and line number of the + // exception. Output the error's stack trace into a string, and + // use the heuristic that the first line number that appears in + // the stack trace is after the first or second colon. We assume that + // this stack frame is the first one the programmer has any control + // over, and so report it as the location of the exception. + // Note that this is implementation-dependent and not robust... + String filename = ""; + int linenum = 0; + try { + StringWriter sw = new StringWriter(); // Output stream to a string. + PrintWriter out=new PrintWriter(sw); // PrintWriter wrapper. + error.printStackTrace(out); // Print stacktrace. + String trace = sw.toString(); // Get it as a string. + int pos = trace.indexOf(':'); // Look for first colon. + if (error.getMessage() != null) // If the error has a message + pos = trace.indexOf(':', pos+1); // look for second colon. + int pos2 = trace.indexOf(')', pos); // Look for end of line # + linenum = Integer.parseInt(trace.substring(pos+1,pos2)); // line # + pos2 = trace.lastIndexOf('(', pos); // Back to start of filename. + filename = trace.substring(pos2+1, pos); // Get filename. + } + catch (Exception e) { ; } // Ignore exceptions. + + // Set up an array of arguments to use with the message + String errmsg = error.getMessage(); + Object[] args = { + ((errmsg!= null)?errmsg:""), error.getClass().getName(), + filename, new Integer(linenum), new Date() + }; + // Finally, display the localized error message, using + // MessageFormat.format() to substitute the arguments into the message. + System.out.println(MessageFormat.format(message, args)); + } + + /** + * This is a simple test program that demonstrates the display() method. + * You can use it to generate and display a FileNotFoundException or an + * ArrayIndexOutOfBoundsException + **/ + public static void main(String[] args) { + try { FileReader in = new FileReader(args[0]); } + catch(Exception e) { LocalizedError.display(e); } + } +} diff --git a/src/main/java/com/davidflanagan/examples/i18n/Menus.properties b/src/main/java/com/davidflanagan/examples/i18n/Menus.properties new file mode 100644 index 0000000..968b4b1 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/i18n/Menus.properties @@ -0,0 +1,9 @@ +# The file Menus.properties is the default "Menus" resource bundle. +# As an American programmer, I made my own locale the default. +colors.label=Colors +colors.red.label=Red +colors.red.accelerator=alt R +colors.green.label=Green +colors.green.accelerator=alt G +colors.blue.label=Blue +colors.blue.accelerator=alt B diff --git a/src/main/java/com/davidflanagan/examples/i18n/Menus_en_GB.properties b/src/main/java/com/davidflanagan/examples/i18n/Menus_en_GB.properties new file mode 100644 index 0000000..ba75f22 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/i18n/Menus_en_GB.properties @@ -0,0 +1,4 @@ +# This is the file Menus_en_GB.properties. It is the resource bundle for +# British English. Note that it overrides only a single resource definition +# and simply inherits the rest from the default (American) bundle. +colors.label=Colours diff --git a/src/main/java/com/davidflanagan/examples/i18n/Menus_fr.properties b/src/main/java/com/davidflanagan/examples/i18n/Menus_fr.properties new file mode 100644 index 0000000..7297b76 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/i18n/Menus_fr.properties @@ -0,0 +1,8 @@ +# This is the file Menus_fr.properties. It is the resource bundle for all +# French-speaking locales. It overrides most, but not all, of the resources +# in the default bundle. +colors.label=Couleurs +colors.red.label=Rouge +colors.green.label=Vert +colors.green.accelerator=control shift V +colors.blue.label=Bleu diff --git a/src/main/java/com/davidflanagan/examples/i18n/Portfolio.java b/src/main/java/com/davidflanagan/examples/i18n/Portfolio.java new file mode 100644 index 0000000..68aa1eb --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/i18n/Portfolio.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.i18n; +import java.text.*; +import java.util.*; +import java.io.*; + +/** + * A partial implementation of a hypothetical stock portfolio class. + * We use it only to demonstrate number and date internationalization. + **/ +public class Portfolio { + EquityPosition[] positions; + Date lastQuoteTime = new Date(); + + public Portfolio(EquityPosition[] positions, Date lastQuoteTime) { + this.positions = positions; + this.lastQuoteTime = lastQuoteTime; + } + + public void print(PrintWriter out) { + // Obtain NumberFormat and DateFormat objects to format our data. + NumberFormat number = NumberFormat.getInstance(); + NumberFormat price = NumberFormat.getCurrencyInstance(); + NumberFormat percent = NumberFormat.getPercentInstance(); + DateFormat shortdate = DateFormat.getDateInstance(DateFormat.MEDIUM); + DateFormat fulldate = DateFormat.getDateTimeInstance(DateFormat.LONG, + DateFormat.LONG); + + // Print some introductory data. + out.println("Portfolio value at " + + fulldate.format(lastQuoteTime) + ":"); + out.println("Symbol\tShares\tPurchased\tAt\t" + + "Quote\tChange"); + + // Display the table using the format() methods of the Format objects. + for(int i = 0; i < positions.length; i++) { + out.print(positions[i].name + "\t"); + out.print(number.format(positions[i].shares) + "\t"); + out.print(shortdate.format(positions[i].purchased) + "\t"); + out.print(price.format(positions[i].bought) + "\t"); + out.print(price.format(positions[i].current) + "\t"); + double change = + (positions[i].current-positions[i].bought)/positions[i].bought; + out.println(percent.format(change)); + out.flush(); + } + } + + static class EquityPosition { + String name; // Name of the stock. + int shares; // Number of shares held. + Date purchased; // When purchased. + double bought; // Purchase price per share + double current; // Current price per share + EquityPosition(String n, int s, Date when, double then, double now) { + name = n; shares = s; purchased = when; + bought = then; current = now; + } + } + + /** + * This is a test program that demonstrates the class + **/ + public static void main(String[] args) { + // This is the portfolio to display. Note we use a deprecated + // Date() constructor here for convenience. It represents the year + // offset from 1900, and will cause a warning message when compiling. + EquityPosition[] positions = new EquityPosition[] { + new EquityPosition("XXX", 400, new Date(100,1,3),11.90,13.00), + new EquityPosition("YYY", 1100, new Date(100,2,2),71.09,27.25), + new EquityPosition("ZZZ", 6000, new Date(100,4,17),23.37,89.12) + }; + + // Create the portfolio from these positions + Portfolio portfolio = new Portfolio(positions, new Date()); + + // Set the default locale using the language code and country code + // specified on the command line. + if (args.length == 2) Locale.setDefault(new Locale(args[0], args[1])); + + // Now print the portfolio + portfolio.print(new PrintWriter(System.out)); + } +} diff --git a/src/main/java/com/davidflanagan/examples/i18n/SimpleMenu.java b/src/main/java/com/davidflanagan/examples/i18n/SimpleMenu.java new file mode 100644 index 0000000..375d358 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/i18n/SimpleMenu.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.i18n; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.MissingResourceException; + +/** A convenience class to automatically create localized menu panes */ +public class SimpleMenu { + /** The convenience method that creates menu panes */ + public static JMenu create(ResourceBundle bundle, + String menuname, String[] itemnames, + ActionListener listener) + { + // Get the menu title from the bundle. Use name as default label. + String menulabel; + try { menulabel = bundle.getString(menuname + ".label"); } + catch(MissingResourceException e) { menulabel = menuname; } + + // Create the menu pane. + JMenu menu = new JMenu(menulabel); + + // For each named item in the menu. + for(int i = 0; i < itemnames.length; i++) { + // Look up the label for the item, using name as default. + String itemlabel; + try { + itemlabel = + bundle.getString(menuname+"."+itemnames[i]+".label"); + } + catch (MissingResourceException e) { itemlabel = itemnames[i]; } + + JMenuItem item = new JMenuItem(itemlabel); + + // Look up an accelerator for the menu item + try { + String acceleratorText = + bundle.getString(menuname+"."+itemnames[i]+".accelerator"); + item.setAccelerator(KeyStroke.getKeyStroke(acceleratorText)); + } + catch (MissingResourceException e) {} + + // Register an action listener and command for the item. + if (listener != null) { + item.addActionListener(listener); + item.setActionCommand(itemnames[i]); + } + + // Add the item to the menu. + menu.add(item); + } + + // Return the automatically created localized menu. + return menu; + } + + /** A simple test program for the above code */ + public static void main(String[] args) { + // Get the locale: default, or specified on command-line + Locale locale; + if (args.length == 2) locale = new Locale(args[0], args[1]); + else locale = Locale.getDefault(); + + // Get the resource bundle for that Locale. This will throw an + // (unchecked) MissingResourceException if no bundle is found. + ResourceBundle bundle = + ResourceBundle.getBundle("com.davidflanagan.examples.i18n.Menus", + locale); + + // Create a simple GUI window to display the menu with + final JFrame f = new JFrame("SimpleMenu: " + // Window title + locale.getDisplayName(Locale.getDefault())); + JMenuBar menubar = new JMenuBar(); // Create a menubar. + f.setJMenuBar(menubar); // Add menubar to window + + // Define an action listener for that our menu will use. + ActionListener listener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + String s = e.getActionCommand(); + Component c = f.getContentPane(); + if (s.equals("red")) c.setBackground(Color.red); + else if (s.equals("green")) c.setBackground(Color.green); + else if (s.equals("blue")) c.setBackground(Color.blue); + } + }; + + // Now create a menu using our convenience routine with the resource + // bundle and action listener we've created + JMenu menu = SimpleMenu.create(bundle, "colors", + new String[] {"red", "green", "blue"}, + listener); + + // Finally add the menu to the GUI, and pop it up + menubar.add(menu); // Add the menu to the menubar + f.setSize(300, 150); // Set the window size. + f.setVisible(true); // Pop the window up. + } +} diff --git a/src/main/java/com/davidflanagan/examples/i18n/UnicodeDisplay.java b/src/main/java/com/davidflanagan/examples/i18n/UnicodeDisplay.java new file mode 100644 index 0000000..01eb46d --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/i18n/UnicodeDisplay.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.i18n; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +/** + * This program displays Unicode glyphs using user-specified fonts + * and font styles. + **/ +public class UnicodeDisplay extends JFrame implements ActionListener { + int page = 0; + UnicodePanel p; + JScrollBar b; + String fontfamily = "Serif"; + int fontstyle = Font.PLAIN; + + /** + * This constructor creates the frame, menubar, and scrollbar + * that work along with the UnicodePanel class, defined below + **/ + public UnicodeDisplay(String name) { + super(name); + p = new UnicodePanel(); // Create the panel + p.setBase((char)(page * 0x100)); // Initialize it + getContentPane().add(p, "Center"); // Center it + + // Create and set up a scrollbar, and put it on the right + b = new JScrollBar(Scrollbar.VERTICAL, 0, 1, 0, 0xFF); + b.setUnitIncrement(1); + b.setBlockIncrement(0x10); + b.addAdjustmentListener(new AdjustmentListener() { + public void adjustmentValueChanged(AdjustmentEvent e) { + page = e.getValue(); + p.setBase((char)(page * 0x100)); + } + }); + getContentPane().add(b, "East"); + + // Set things up so we respond to window close requests + this.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { System.exit(0); } + }); + + // Handle Page Up and Page Down and the up and down arrow keys + this.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + int code = e.getKeyCode(); + int oldpage = page; + if ((code == KeyEvent.VK_PAGE_UP) || + (code == KeyEvent.VK_UP)) { + if (e.isShiftDown()) page -= 0x10; + else page -= 1; + if (page < 0) page = 0; + } + else if ((code == KeyEvent.VK_PAGE_DOWN) || + (code == KeyEvent.VK_DOWN)) { + if (e.isShiftDown()) page += 0x10; + else page += 1; + if (page > 0xff) page = 0xff; + } + if (page != oldpage) { // if anything has changed... + p.setBase((char) (page * 0x100)); // update the display + b.setValue(page); // and update scrollbar to match + } + } + }); + + // Set up a menu system to change fonts. Use a convenience method. + JMenuBar menubar = new JMenuBar(); + this.setJMenuBar(menubar); + menubar.add(makemenu("Font Family", + new String[] {"Serif", "SansSerif", "Monospaced"}, + this)); + menubar.add(makemenu("Font Style", + new String[]{ + "Plain","Italic","Bold","BoldItalic" + }, this)); + } + + /** This method handles the items in the menubars */ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("Serif")) fontfamily = "Serif"; + else if (cmd.equals("SansSerif")) fontfamily = "SansSerif"; + else if (cmd.equals("Monospaced")) fontfamily = "Monospaced"; + else if (cmd.equals("Plain")) fontstyle = Font.PLAIN; + else if (cmd.equals("Italic")) fontstyle = Font.ITALIC; + else if (cmd.equals("Bold")) fontstyle = Font.BOLD; + else if (cmd.equals("BoldItalic")) fontstyle = Font.BOLD + Font.ITALIC; + p.setFont(fontfamily, fontstyle); + } + + /** A convenience method to create a Menu from an array of items */ + private JMenu makemenu(String name, String[] itemnames, + ActionListener listener) + { + JMenu m = new JMenu(name); + for(int i = 0; i < itemnames.length; i++) { + JMenuItem item = new JMenuItem(itemnames[i]); + item.addActionListener(listener); + item.setActionCommand(itemnames[i]); // okay here, though + m.add(item); + } + return m; + } + + /** The main() program just create a window, packs it, and shows it */ + public static void main(String[] args) { + UnicodeDisplay f = new UnicodeDisplay("Unicode Displayer"); + f.pack(); + f.show(); + } + + /** + * This nested class is the one that displays one "page" of Unicode + * glyphs at a time. Each "page" is 256 characters, arranged into 16 + * rows of 16 columns each. + **/ + public static class UnicodePanel extends JComponent { + protected char base; // What character we start the display at + protected Font font = new Font("serif", Font.PLAIN, 18); + protected Font headingfont = new Font("monospaced", Font.BOLD, 18); + static final int lineheight = 25; + static final int charspacing = 20; + static final int x0 = 65; + static final int y0 = 40; + + /** Specify where to begin displaying, and re-display */ + public void setBase(char base) { this.base = base; repaint(); } + + /** Set a new font name or style, and redisplay */ + public void setFont(String family, int style) { + this.font = new Font(family, style, 18); + repaint(); + } + + /** + * The paintComponent() method actually draws the page of glyphs + **/ + public void paintComponent(Graphics g) { + int start = (int)base & 0xFFF0; // Start on a 16-character boundary + + // Draw the headings in a special font + g.setFont(headingfont); + + // Draw 0..F on top + for(int i=0; i < 16; i++) { + String s = Integer.toString(i, 16); + g.drawString(s, x0 + i*charspacing, y0-20); + } + + // Draw column down left. + for(int i = 0; i < 16; i++) { + int j = start + i*16; + String s = Integer.toString(j, 16); + g.drawString(s, 10, y0+i*lineheight); + } + + // Now draw the characters + g.setFont(font); + char[] c = new char[1]; + for(int i = 0; i < 16; i++) { + for(int j = 0; j < 16; j++) { + c[0] = (char)(start + j*16 + i); + g.drawChars(c, 0, 1, x0 + i*charspacing, y0+j*lineheight); + } + } + } + + /** Custom components like this one should always have this method */ + public Dimension getPreferredSize() { + return new Dimension(x0 + 16*charspacing, + y0 + 16*lineheight); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/io/Compress.java b/src/main/java/com/davidflanagan/examples/io/Compress.java new file mode 100644 index 0000000..e67f472 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/io/Compress.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.io; +import java.io.*; +import java.util.zip.*; + +/** + * This class defines two static methods for gzipping files and zipping + * directories. It also defines a demonstration program as a nested class. + **/ +public class Compress { + /** Gzip the contents of the from file and save in the to file. */ + public static void gzipFile(String from, String to) throws IOException { + // Create stream to read from the from file + FileInputStream in = new FileInputStream(from); + // Create stream to compress data and write it to the to file. + GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(to)); + // Copy bytes from one stream to the other + byte[] buffer = new byte[4096]; + int bytes_read; + while((bytes_read = in.read(buffer)) != -1) + out.write(buffer, 0, bytes_read); + // And close the streams + in.close(); + out.close(); + } + + /** Zip the contents of the directory, and save it in the zipfile */ + public static void zipDirectory(String dir, String zipfile) + throws IOException, IllegalArgumentException { + // Check that the directory is a directory, and get its contents + File d = new File(dir); + if (!d.isDirectory()) + throw new IllegalArgumentException("Compress: not a directory: " + + dir); + String[] entries = d.list(); + byte[] buffer = new byte[4096]; // Create a buffer for copying + int bytes_read; + + // Create a stream to compress data and write it to the zipfile + ZipOutputStream out = + new ZipOutputStream(new FileOutputStream(zipfile)); + + // Loop through all entries in the directory + for(int i = 0; i < entries.length; i++) { + File f = new File(d, entries[i]); + if (f.isDirectory()) continue; // Don't zip sub-directories + FileInputStream in = new FileInputStream(f); // Stream to read file + ZipEntry entry = new ZipEntry(f.getPath()); // Make a ZipEntry + out.putNextEntry(entry); // Store entry + while((bytes_read = in.read(buffer)) != -1) // Copy bytes + out.write(buffer, 0, bytes_read); + in.close(); // Close input stream + } + // When we're done with the whole loop, close the output stream + out.close(); + } + + /** + * This nested class is a test program that demonstrates the use of the + * static methods defined above. + **/ + public static class Test { + /** + * Compress a specified file or directory. If no destination name is + * specified, append .gz to a file name or .zip to a directory name + **/ + public static void main(String args[]) throws IOException { + if ((args.length != 1)&& (args.length != 2)) { // check arguments + System.err.println("Usage: java Compress$Test []"); + System.exit(0); + } + String from = args[0], to; + File f = new File(from); + boolean directory = f.isDirectory(); // Is it a file or directory? + if (args.length == 2) to = args[1]; + else { // If destination not specified + if (directory) to = from + ".zip"; // use a .zip suffix + else to = from + ".gz"; // or a .gz suffix + } + + if ((new File(to)).exists()) { // Make sure not to overwrite + System.err.println("Compress: won't overwrite existing file: "+ + to); + System.exit(0); + } + + // Finally, call one of the methods defined above to do the work. + if (directory) Compress.zipDirectory(from, to); + else Compress.gzipFile(from, to); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/io/Delete.java b/src/main/java/com/davidflanagan/examples/io/Delete.java new file mode 100644 index 0000000..777ddfd --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/io/Delete.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.io; +import java.io.*; + +/** + * This class is a static method delete() and a standalone program that + * deletes a specified file or directory. + **/ +public class Delete { + /** + * This is the main() method of the standalone program. After checking + * it arguments, it invokes the Delete.delete() method to do the deletion + **/ + public static void main(String[] args) { + if (args.length != 1) { // Check command-line arguments + System.err.println("Usage: java Delete "); + System.exit(0); + } + // Call delete() and display any error messages it throws. + try { delete(args[0]); } + catch (IllegalArgumentException e) { + System.err.println(e.getMessage()); + } + } + + /** + * The static method that does the deletion. Invoked by main(), and + * designed for use by other programs as well. It first makes sure that + * the specified file or directory is deleteable before attempting to + * delete it. If there is a problem, it throws an + * IllegalArgumentException. + **/ + public static void delete(String filename) { + // Create a File object to represent the filename + File f = new File(filename); + + // Make sure the file or directory exists and isn't write protected + if (!f.exists()) fail("Delete: no such file or directory: " +filename); + if (!f.canWrite()) fail("Delete: write protected: " + filename); + + // If it is a directory, make sure it is empty + if (f.isDirectory()) { + String[] files = f.list(); + if (files.length > 0) + fail("Delete: directory not empty: " + filename); + } + + // If we passed all the tests, then attempt to delete it + boolean success = f.delete(); + + // And throw an exception if it didn't work for some (unknown) reason. + // For example, because of a bug with Java 1.1.1 on Linux, + // directory deletion always fails + if (!success) fail("Delete: deletion failed"); + } + + /** A convenience method to throw an exception */ + protected static void fail(String msg) throws IllegalArgumentException { + throw new IllegalArgumentException(msg); + } +} diff --git a/src/main/java/com/davidflanagan/examples/io/FileCopy.java b/src/main/java/com/davidflanagan/examples/io/FileCopy.java new file mode 100644 index 0000000..bcdda97 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/io/FileCopy.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.io; +import java.io.*; + +/** + * This class is a standalone program to copy a file, and also defines a + * static copy() method that other programs can use to copy files. + **/ +public class FileCopy { + /** The main() method of the standalone program. Calls copy(). */ + public static void main(String[] args) { + if (args.length != 2) // Check arguments + System.err.println("Usage: java FileCopy "); + else { + // Call copy() to do the copy; display any error messages + try { copy(args[0], args[1]); } + catch (IOException e) { System.err.println(e.getMessage()); } + } + } + + /** + * The static method that actually performs the file copy. + * Before copying the file, however, it performs a lot of tests to make + * sure everything is as it should be. + */ + public static void copy(String from_name, String to_name) + throws IOException + { + File from_file = new File(from_name); // Get File objects from Strings + File to_file = new File(to_name); + + // First make sure the source file exists, is a file, and is readable. + if (!from_file.exists()) + abort("no such source file: " + from_name); + if (!from_file.isFile()) + abort("can't copy directory: " + from_name); + if (!from_file.canRead()) + abort("source file is unreadable: " + from_name); + + // If the destination is a directory, use the source file name + // as the destination file name + if (to_file.isDirectory()) + to_file = new File(to_file, from_file.getName()); + + // If the destination exists, make sure it is a writeable file + // and ask before overwriting it. If the destination doesn't + // exist, make sure the directory exists and is writeable. + if (to_file.exists()) { + if (!to_file.canWrite()) + abort("destination file is unwriteable: " + to_name); + // Ask whether to overwrite it + System.out.print("Overwrite existing file " + to_file.getName() + + "? (Y/N): "); + System.out.flush(); + // Get the user's response. + BufferedReader in= + new BufferedReader(new InputStreamReader(System.in)); + String response = in.readLine(); + // Check the response. If not a Yes, abort the copy. + if (!response.equals("Y") && !response.equals("y")) + abort("existing file was not overwritten."); + } + else { + // If file doesn't exist, check if directory exists and is + // writeable. If getParent() returns null, then the directory is + // the current dir. so look up the user.dir system property to + // find out what that is. + String parent = to_file.getParent(); // The destination directory + if (parent == null) // If none, use the current directory + parent = System.getProperty("user.dir"); + File dir = new File(parent); // Convert it to a file. + if (!dir.exists()) + abort("destination directory doesn't exist: "+parent); + if (dir.isFile()) + abort("destination is not a directory: " + parent); + if (!dir.canWrite()) + abort("destination directory is unwriteable: " + parent); + } + + // If we've gotten this far, then everything is okay. + // So we copy the file, a buffer of bytes at a time. + FileInputStream from = null; // Stream to read from source + FileOutputStream to = null; // Stream to write to destination + try { + from = new FileInputStream(from_file); // Create input stream + to = new FileOutputStream(to_file); // Create output stream + byte[] buffer = new byte[4096]; // To hold file contents + int bytes_read; // How many bytes in buffer + + // Read a chunk of bytes into the buffer, then write them out, + // looping until we reach the end of the file (when read() returns + // -1). Note the combination of assignment and comparison in this + // while loop. This is a common I/O programming idiom. + while((bytes_read = from.read(buffer)) != -1) // Read until EOF + to.write(buffer, 0, bytes_read); // write + } + // Always close the streams, even if exceptions were thrown + finally { + if (from != null) try { from.close(); } catch (IOException e) { ; } + if (to != null) try { to.close(); } catch (IOException e) { ; } + } + } + + /** A convenience method to throw an exception */ + private static void abort(String msg) throws IOException { + throw new IOException("FileCopy: " + msg); + } +} diff --git a/src/main/java/com/davidflanagan/examples/io/FileLister.java b/src/main/java/com/davidflanagan/examples/io/FileLister.java new file mode 100644 index 0000000..b0aea5f --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/io/FileLister.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.io; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.text.DateFormat; +import java.util.Date; + +/** + * This class creates and displays a window containing a list of + * files and sub-directories in a specified directory. Clicking on an + * entry in the list displays more information about it. Double-clicking + * on an entry displays it, if a file, or lists it if a directory. + * An optionally-specified FilenameFilter filters the displayed list. + **/ +public class FileLister extends Frame implements ActionListener, ItemListener { + private List list; // To display the directory contents in + private TextField details; // To display detail info in. + private Panel buttons; // Holds the buttons + private Button up, close; // The Up and Close buttons + private File currentDir; // The directory currently listed + private FilenameFilter filter; // An optional filter for the directory + private String[] files; // The directory contents + private DateFormat dateFormatter = // To display dates and time correctly + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); + + /** + * Constructor: create the GUI, and list the initial directory. + **/ + public FileLister(String directory, FilenameFilter filter) { + super("File Lister"); // Create the window + this.filter = filter; // Save the filter, if any + + // Destroy the window when the user requests it + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { dispose(); } + }); + + list = new List(12, false); // Set up the list + list.setFont(new Font("MonoSpaced", Font.PLAIN, 14)); + list.addActionListener(this); + list.addItemListener(this); + + details = new TextField(); // Set up the details area + details.setFont(new Font("MonoSpaced", Font.PLAIN, 12)); + details.setEditable(false); + + buttons = new Panel(); // Set up the button box + buttons.setLayout(new FlowLayout(FlowLayout.RIGHT, 15, 5)); + buttons.setFont(new Font("SansSerif", Font.BOLD, 14)); + + up = new Button("Up a Directory"); // Set up the two buttons + close = new Button("Close"); + up.addActionListener(this); + close.addActionListener(this); + + buttons.add(up); // Add buttons to button box + buttons.add(close); + + this.add(list, "Center"); // Add stuff to the window + this.add(details, "North"); + this.add(buttons, "South"); + this.setSize(500, 350); + + listDirectory(directory); // And now list initial directory. + } + + /** + * This method uses the list() method to get all entries in a directory + * and then displays them in the List component. + **/ + public void listDirectory(String directory) { + // Convert the string to a File object, and check that the dir exists + File dir = new File(directory); + if (!dir.isDirectory()) + throw new IllegalArgumentException("FileLister: no such directory"); + + // Get the (filtered) directory entries + files = dir.list(filter); + + // Sort the list of filenames. Prior to Java 1.2, you could use + // com.davidflanagan.examples.classes.Sorter.sort() to sort instead. + java.util.Arrays.sort(files); + + // Remove any old entries in the list, and add the new ones + list.removeAll(); + list.add("[Up to Parent Directory]"); // A special case entry + for(int i = 0; i < files.length; i++) list.add(files[i]); + + // Display directory name in window titlebar and in the details box + this.setTitle(directory); + details.setText(directory); + + // Remember this directory for later. + currentDir = dir; + } + + /** + * This ItemListener method uses various File methods to obtain information + * about a file or directory. Then it displays that info. + **/ + public void itemStateChanged(ItemEvent e) { + int i = list.getSelectedIndex() - 1; // minus 1 for Up To Parent entry + if (i < 0) return; + String filename = files[i]; // Get the selected entry + File f = new File(currentDir, filename); // Convert to a File + if (!f.exists()) // Confirm that it exists + throw new IllegalArgumentException("FileLister: " + + "no such file or directory"); + + // Get the details about the file or directory, concatenate to a string + String info = filename; + if (f.isDirectory()) info += File.separator; + info += " " + f.length() + " bytes "; + info += dateFormatter.format(new Date(f.lastModified())); + if (f.canRead()) info += " Read"; + if (f.canWrite()) info += " Write"; + + // And display the details string + details.setText(info); + } + + /** + * This ActionListener method is invoked when the user double-clicks on an + * entry or clicks on one of the buttons. If they double-click on a file, + * create a FileViewer to display that file. If they double-click on a + * directory, call the listDirectory() method to display that directory + **/ + public void actionPerformed(ActionEvent e) { + if (e.getSource() == close) this.dispose(); + else if (e.getSource() == up) { up(); } + else if (e.getSource() == list) { // Double click on an item + int i = list.getSelectedIndex(); // Check which item + if (i == 0) up(); // Handle first Up To Parent item + else { // Otherwise, get filename + String name = files[i-1]; + File f = new File(currentDir, name); // Convert to a File + String fullname = f.getAbsolutePath(); + if (f.isDirectory()) listDirectory(fullname); // List dir + else new FileViewer(fullname).show(); // display file + } + } + } + + /** A convenience method to display the contents of the parent directory */ + protected void up() { + String parent = currentDir.getParent(); + if (parent == null) return; + listDirectory(parent); + } + + /** A convenience method used by main() */ + public static void usage() { + System.out.println("Usage: java FileLister [directory_name] " + + "[-e file_extension]"); + System.exit(0); + } + + /** + * A main() method so FileLister can be run standalone. + * Parse command line arguments and create the FileLister object. + * If an extension is specified, create a FilenameFilter for it. + * If no directory is specified, use the current directory. + **/ + public static void main(String args[]) throws IOException { + FileLister f; + FilenameFilter filter = null; // The filter, if any + String directory = null; // The specified dir, or the current dir + + // Loop through args array, parsing arguments + for(int i = 0; i < args.length; i++) { + if (args[i].equals("-e")) { + if (++i >= args.length) usage(); + final String suffix = args[i]; // final for anon. class below + + // This class is a simple FilenameFilter. It defines the + // accept() method required to determine whether a specified + // file should be listed. A file will be listed if its name + // ends with the specified extension, or if it is a directory. + filter = new FilenameFilter() { + public boolean accept(File dir, String name) { + if (name.endsWith(suffix)) return true; + else return (new File(dir, name)).isDirectory(); + } + }; + } + else { + if (directory != null) usage(); // If already specified, fail. + else directory = args[i]; + } + } + + // if no directory specified, use the current directory + if (directory == null) directory = System.getProperty("user.dir"); + // Create the FileLister object, with directory and filter specified. + f = new FileLister(directory, filter); + // Arrange for the application to exit when the window is closed + f.addWindowListener(new WindowAdapter() { + public void windowClosed(WindowEvent e) { System.exit(0); } + }); + // Finally, pop the window up up. + f.show(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/io/FileViewer.java b/src/main/java/com/davidflanagan/examples/io/FileViewer.java new file mode 100644 index 0000000..5f4aa53 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/io/FileViewer.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.io; +import java.awt.*; +import java.awt.event.*; +import java.io.*; + +/** + * This class creates and displays a window containing a TextArea, + * in which the contents of a text file are displayed. + **/ +public class FileViewer extends Frame implements ActionListener { + String directory; // The default directory to display in the FileDialog + TextArea textarea; // The area to display the file contents into + + /** Convenience constructor: file viewer starts out blank */ + public FileViewer() { this(null, null); } + /** Convenience constructor: display file from current directory */ + public FileViewer(String filename) { this(null, filename); } + + /** + * The real constructor. Create a FileViewer object to display the + * specified file from the specified directory + **/ + public FileViewer(String directory, String filename) { + super(); // Create the frame + + // Destroy the window when the user requests it + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { dispose(); } + }); + + // Create a TextArea to display the contents of the file in + textarea = new TextArea("", 24, 80); + textarea.setFont(new Font("MonoSpaced", Font.PLAIN, 12)); + textarea.setEditable(false); + this.add("Center", textarea); + + // Create a bottom panel to hold a couple of buttons in + Panel p = new Panel(); + p.setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 5)); + this.add(p, "South"); + + // Create the buttons and arrange to handle button clicks + Font font = new Font("SansSerif", Font.BOLD, 14); + Button openfile = new Button("Open File"); + Button close = new Button("Close"); + openfile.addActionListener(this); + openfile.setActionCommand("open"); + openfile.setFont(font); + close.addActionListener(this); + close.setActionCommand("close"); + close.setFont(font); + p.add(openfile); + p.add(close); + + this.pack(); + + // Figure out the directory, from filename or current dir, if necessary + if (directory == null) { + File f; + if ((filename != null)&& (f = new File(filename)).isAbsolute()) { + directory = f.getParent(); + filename = f.getName(); + } + else directory = System.getProperty("user.dir"); + } + + this.directory = directory; // Remember the directory, for FileDialog + setFile(directory, filename); // Now load and display the file + } + + /** + * Load and display the specified file from the specified directory + **/ + public void setFile(String directory, String filename) { + if ((filename == null) || (filename.length() == 0)) return; + File f; + FileReader in = null; + // Read and display the file contents. Since we're reading text, we + // use a FileReader instead of a FileInputStream. + try { + f = new File(directory, filename); // Create a file object + in = new FileReader(f); // And a char stream to read it + char[] buffer = new char[4096]; // Read 4K characters at a time + int len; // How many chars read each time + textarea.setText(""); // Clear the text area + while((len = in.read(buffer)) != -1) { // Read a batch of chars + String s = new String(buffer, 0, len); // Convert to a string + textarea.append(s); // And display them + } + this.setTitle("FileViewer: " + filename); // Set the window title + textarea.setCaretPosition(0); // Go to start of file + } + // Display messages if something goes wrong + catch (IOException e) { + textarea.setText(e.getClass().getName() + ": " + e.getMessage()); + this.setTitle("FileViewer: " + filename + ": I/O Exception"); + } + // Always be sure to close the input stream! + finally { try { if (in!=null) in.close(); } catch (IOException e) {} } + } + + /** + * Handle button clicks + **/ + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("open")) { // If user clicked "Open" button + // Create a file dialog box to prompt for a new file to display + FileDialog f = new FileDialog(this, "Open File", FileDialog.LOAD); + f.setDirectory(directory); // Set the default directory + + // Display the dialog and wait for the user's response + f.show(); + + directory = f.getDirectory(); // Remember new default directory + setFile(directory, f.getFile()); // Load and display selection + f.dispose(); // Get rid of the dialog box + } + else if (cmd.equals("close")) // If user clicked "Close" button + this.dispose(); // then close the window + } + + /** + * The FileViewer can be used by other classes, or it can be + * used standalone with this main() method. + **/ + static public void main(String[] args) throws IOException { + // Create a FileViewer object + Frame f = new FileViewer((args.length == 1)?args[0]:null); + // Arrange to exit when the FileViewer window closes + f.addWindowListener(new WindowAdapter() { + public void windowClosed(WindowEvent e) { System.exit(0); } + }); + // And pop the window up + f.show(); + } +} + + diff --git a/src/main/java/com/davidflanagan/examples/io/GrepReader.java b/src/main/java/com/davidflanagan/examples/io/GrepReader.java new file mode 100644 index 0000000..875424e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/io/GrepReader.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.io; +import java.io.*; + +/** + * This class is a BufferedReader that filters out all lines that + * do not contain the specified pattern. + **/ +public class GrepReader extends BufferedReader { + String pattern; // The string we are going to be matching. + + /** Pass the stream to our superclass, and remember the pattern ourself */ + public GrepReader(Reader in, String pattern) { + super(in); + this.pattern = pattern; + } + + /** + * This is the filter: call our superclass's readLine() to get the + * actual lines, but only return lines that contain the pattern. + * When the superclass readLine() returns null (EOF), we return null. + **/ + public final String readLine() throws IOException { + String line; + do { line = super.readLine(); } + while ((line != null)&& line.indexOf(pattern) == -1); + return line; + } + + /** + * This class demonstrates the use of the GrepReader class. + * It prints the lines of a file that contain a specified substring. + **/ + public static class Test { + public static void main(String args[]) { + try { + if (args.length != 2) + throw new IllegalArgumentException("Wrong number of args"); + GrepReader in=new GrepReader(new FileReader(args[1]), args[0]); + String line; + while((line = in.readLine()) != null) System.out.println(line); + in.close(); + } + catch (Exception e) { + System.err.println(e); + System.out.println("Usage: java GrepReader$Test" + + " "); + } + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/io/HTMLWriter.java b/src/main/java/com/davidflanagan/examples/io/HTMLWriter.java new file mode 100644 index 0000000..c6c9805 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/io/HTMLWriter.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.io; +import java.io.*; +import java.net.*; +import java.applet.Applet; +import netscape.javascript.JSObject; // A special class we need + +/** + * An output stream that sends HTML text to a newly created web browser window. + * It relies on the netscape.javascript.JSObject class to send JavaScript + * commands to the Web browser, and only works for applets running in + * the Netscape Navigator Web browser. + **/ +public class HTMLWriter extends Writer { + JSObject main_window; // the initial browser window + JSObject window; // the new window we create + JSObject document; // the document of that new window + static int window_num = 0; // used to give each new window a unique name + + /** + * When you create a new HTMLWriter, it pops up a new, blank, Web browser + * window to display the output in. You must specify the applet + * (this specifies the main browser window) and the desired size + * for the new window. + **/ + public HTMLWriter(Applet applet, int width, int height) { + // Verify that we can find the JSObject class we need. Warn if not. + try { Class c = Class.forName("netscape.javascript.JSObject"); } + catch (ClassNotFoundException e) { + throw new NoClassDefFoundError("HTMLWriter requires " + + "Netscape Navigator 4.0 or higher " + + "or a browser that supports LiveConnect technology"); + } + + // Get a reference to the main browser window from the applet. + main_window = JSObject.getWindow(applet); + + // Create a new window to display output in. This command sends a + // string of JavaScript to the web browser + window = (JSObject) + main_window.eval("self.open(''," + + "'HTMLWriter" + window_num++ + "'," + + "'menubar,status,resizable,scrollbars," + + "width=" + width + ",height=" + height + "')"); + + // Obtain the Document object of this new window, and open it. + document = (JSObject) window.getMember("document"); + document.call("open", null); + } + + /** + * This is the write() method required for all Writer subclasses. + * Writer defines all its other write() methods in terms of this one. + **/ + public void write(char[] buf, int offset, int length) { + // If no window or document, do nothing. This occurs if the stream + // has been closed, or if the code is not running in Navigator. + if ((window == null) || (document == null)) return; + // If the window has been closed by the user, do nothing + if (((Boolean)window.getMember("closed")).booleanValue()) return; + // Otherwise, create a string from the specified bytes + String s = new String(buf, offset, length); + // And pass it to the JS document.write() method to output the HTML + document.call("write", new String[] { s }); + } + + /** + * There is no general way to force JavaScript to flush all pending output, + * so this method does nothing. To flush, output a

tag or some other + * HTML tag that forces a line break in the output. + **/ + public void flush() {} + + /** + * When the stream is closed, close the JavaScript Document object + * (But don't close the window yet.) + **/ + public void close() { document.call("close", null); document = null; } + + /** + * If the browser window is still open, close it. + * This method is unique to HTMLWriter. + **/ + public void closeWindow() { + if (document != null) close(); + if (!((Boolean)window.getMember("closed")).booleanValue()) + window.call("close", null); + window = null; + } + + /** A finalizer method to close the window in case we forget. */ + public void finalize() { closeWindow(); } + + /** + * This nested class is an applet that demonstrates the use of HTMLWriter. + * It reads the contents of the URL specified in its url parameter and + * writes them out to an HTMLWriter stream. It will only work in + * Netscape 4.0 or later. It requires an tag like this: + * + * + * + * Note that MAYSCRIPT attribute. It is required to enable the applet + * to invoke JavaScript. + **/ + public static class Test extends Applet { + HTMLWriter out; + /** When the applet starts, read and display specified URL */ + public void init() { + try { + // Get the URL specified in the tag + URL url = + new URL(this.getDocumentBase(), this.getParameter("url")); + // Get a stream to read its contents + Reader in = new InputStreamReader(url.openStream()); + // Create an HTMLWriter stream for out output + out = new HTMLWriter(this, 400, 200); + // Read buffers of characters and output them to the HTMLWriter + char[] buffer = new char[4096]; + int numchars; + while((numchars = in.read(buffer)) != -1) + out.write(buffer, 0, numchars); + // Close the streams + in.close(); + out.close(); + } + catch (IOException e) {} + } + /** When the applet terminates, close the window we created */ + public void destroy() { out.closeWindow(); } + } +} diff --git a/src/main/java/com/davidflanagan/examples/io/RemoveHTMLReader.java b/src/main/java/com/davidflanagan/examples/io/RemoveHTMLReader.java new file mode 100644 index 0000000..23b4779 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/io/RemoveHTMLReader.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.io; +import java.io.*; + +/** + * A simple FilterReader that strips HTML tags (or anything between + * pairs of angle brackets) out of a stream of characters. + **/ +public class RemoveHTMLReader extends FilterReader { + /** A trivial constructor. Just initialize our superclass */ + public RemoveHTMLReader(Reader in) { super(in); } + + boolean intag = false; // Used to remember whether we are "inside" a tag + + /** + * This is the implementation of the no-op read() method of FilterReader. + * It calls in.read() to get a buffer full of characters, then strips + * out the HTML tags. (in is a protected field of the superclass). + **/ + public int read(char[] buf, int from, int len) throws IOException { + int numchars = 0; // how many characters have been read + // Loop, because we might read a bunch of characters, then strip them + // all out, leaving us with zero characters to return. + while (numchars == 0) { + numchars = in.read(buf, from, len); // Read characters + if (numchars == -1) return -1; // Check for EOF and handle it. + + // Loop through the characters we read, stripping out HTML tags. + // Characters not in tags are copied over previous tags + int last = from; // Index of last non-HTML char + for(int i = from; i < from + numchars; i++) { + if (!intag) { // If not in an HTML tag + if (buf[i] == '<') intag = true; // check for tag start + else buf[last++] = buf[i]; // and copy the character + } + else if (buf[i] == '>') intag = false; // check for end of tag + } + numchars = last - from; // Figure out how many characters remain + } // And if it is more than zero characters + return numchars; // Then return that number. + } + + /** + * This is another no-op read() method we have to implement. We + * implement it in terms of the method above. Our superclass implements + * the remaining read() methods in terms of these two. + **/ + public int read() throws IOException { + char[] buf = new char[1]; + int result = read(buf, 0, 1); + if (result == -1) return -1; + else return (int)buf[0]; + } + + /** This class defines a main() method to test the RemoveHTMLReader */ + public static class Test { + /** The test program: read a text file, strip HTML, print to console */ + public static void main(String[] args) { + try { + if (args.length != 1) + throw new IllegalArgumentException("Wrong number of args"); + // Create a stream to read from the file and strip tags from it + BufferedReader in = new BufferedReader( + new RemoveHTMLReader(new FileReader(args[0]))); + // Read line by line, printing lines to the console + String line; + while((line = in.readLine()) != null) + System.out.println(line); + in.close(); // Close the stream. + } + catch(Exception e) { + System.err.println(e); + System.err.println("Usage: java RemoveHTMLReader$Test" + + " "); + } + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/GenericClient.java b/src/main/java/com/davidflanagan/examples/net/GenericClient.java new file mode 100644 index 0000000..6c31eb9 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/GenericClient.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; + +/** + * This program connects to a server at a specified host and port. + * It reads text from the console and sends it to the server. + * It reads text from the server and sends it to the console. + **/ +public class GenericClient { + public static void main(String[] args) throws IOException { + try { + // Check the number of arguments + if (args.length != 2) + throw new IllegalArgumentException("Wrong number of args"); + + // Parse the host and port specifications + String host = args[0]; + int port = Integer.parseInt(args[1]); + + // Connect to the specified host and port + Socket s = new Socket(host, port); + + // Set up streams for reading from and writing to the server. + // The from_server stream is final for use in the inner class below + final Reader from_server=new InputStreamReader(s.getInputStream()); + PrintWriter to_server = new PrintWriter(s.getOutputStream()); + + // Set up streams for reading from and writing to the console + // The to_user stream is final for use in the anonymous class below + BufferedReader from_user = + new BufferedReader(new InputStreamReader(System.in)); + // Pass true for auto-flush on println() + final PrintWriter to_user = new PrintWriter(System.out, true); + + // Tell the user that we've connected + to_user.println("Connected to " + s.getInetAddress() + + ":" + s.getPort()); + + // Create a thread that gets output from the server and displays + // it to the user. We use a separate thread for this so that we + // can receive asynchronous output + Thread t = new Thread() { + public void run() { + char[] buffer = new char[1024]; + int chars_read; + try { + // Read characters until the stream closes + while((chars_read = from_server.read(buffer)) != -1) { + // Loop through the array of characters, and + // print them out, converting all \n characters + // to the local platform's line terminator. + // This could be more efficient, but it is probably + // faster than the network is, which is good enough + for(int i = 0; i < chars_read; i++) { + if (buffer[i] == '\n') to_user.println(); + else to_user.print(buffer[i]); + } + to_user.flush(); + } + } + catch (IOException e) { to_user.println(e); } + + // When the server closes the connection, the loop above + // will end. Tell the user what happened, and call + // System.exit(), causing the main thread to exit along + // with this one. + to_user.println("Connection closed by server."); + System.exit(0); + } + }; + + // We set the priority of the server-to-user thread above to be + // one level higher than the main thread. We shouldn't have to do + // this, but on some operating systems, output sent to the console + // doesn't appear when a thread at the same priority level is + // blocked waiting for input from the console. + t.setPriority(Thread.currentThread().getPriority() + 1); + + // Now start the server-to-user thread + t.start(); + + // In parallel, read the user's input and pass it on to the server. + String line; + while((line = from_user.readLine()) != null) { + to_server.print(line + "\n"); + to_server.flush(); + } + + // If the user types a Ctrl-D (Unix) or Ctrl-Z (Windows) to end + // their input, we'll get an EOF, and the loop above will exit. + // When this happens, we stop the server-to-user thread and close + // the socket. + + s.close(); + to_user.println("Connection closed by client."); + System.exit(0); + } + // If anything goes wrong, print an error message + catch (Exception e) { + System.err.println(e); + System.err.println("Usage: java GenericClient "); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/GetURL.java b/src/main/java/com/davidflanagan/examples/net/GetURL.java new file mode 100644 index 0000000..e987509 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/GetURL.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; + +/** + * This simple program uses the URL class and its openStream() method to + * download the contents of a URL and copy them to a file or to the console. + **/ +public class GetURL { + public static void main(String[] args) { + InputStream in = null; + OutputStream out = null; + try { + // Check the arguments + if ((args.length != 1)&& (args.length != 2)) + throw new IllegalArgumentException("Wrong number of args"); + + // Set up the streams + URL url = new URL(args[0]); // Create the URL + in = url.openStream(); // Open a stream to it + if (args.length == 2) // Get an appropriate output stream + out = new FileOutputStream(args[1]); + else out = System.out; + + // Now copy bytes from the URL to the output stream + byte[] buffer = new byte[4096]; + int bytes_read; + while((bytes_read = in.read(buffer)) != -1) + out.write(buffer, 0, bytes_read); + } + // On exceptions, print error message and usage message. + catch (Exception e) { + System.err.println(e); + System.err.println("Usage: java GetURL []"); + } + finally { // Always close the streams, no matter what. + try { in.close(); out.close(); } catch (Exception e) {} + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/GetURLInfo.java b/src/main/java/com/davidflanagan/examples/net/GetURLInfo.java new file mode 100644 index 0000000..dc60a2e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/GetURLInfo.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.net.*; +import java.io.*; +import java.util.Date; + +/** + * A class that displays information about a URL. + **/ +public class GetURLInfo { + /** Use the URLConnection class to get info about the URL */ + public static void printinfo(URL url) throws IOException { + URLConnection c = url.openConnection(); // Get URLConnection from URL + c.connect(); // Open a connection to URL + + // Display some information about the URL contents + System.out.println(" Content Type: " + c.getContentType()); + System.out.println(" Content Encoding: " + c.getContentEncoding()); + System.out.println(" Content Length: " + c.getContentLength()); + System.out.println(" Date: " + new Date(c.getDate())); + System.out.println(" Last Modified: " +new Date(c.getLastModified())); + System.out.println(" Expiration: " + new Date(c.getExpiration())); + + // If it is an HTTP connection, display some additional information. + if (c instanceof HttpURLConnection) { + HttpURLConnection h = (HttpURLConnection) c; + System.out.println(" Request Method: " + h.getRequestMethod()); + System.out.println(" Response Message: " +h.getResponseMessage()); + System.out.println(" Response Code: " + h.getResponseCode()); + } + } + + /** Create a URL, call printinfo() to display information about it. */ + public static void main(String[] args) { + try { printinfo(new URL(args[0])); } + catch (Exception e) { + System.err.println(e); + System.err.println("Usage: java GetURLInfo "); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/HttpClient.java b/src/main/java/com/davidflanagan/examples/net/HttpClient.java new file mode 100644 index 0000000..2504551 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/HttpClient.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; + +/** + * This program connects to a Web server and downloads the specified URL + * from it. It uses the HTTP protocol directly. + **/ +public class HttpClient { + public static void main(String[] args) { + try { + // Check the arguments + if ((args.length != 1) && (args.length != 2)) + throw new IllegalArgumentException("Wrong number of args"); + + // Get an output stream to write the URL contents to + OutputStream to_file; + if (args.length == 2) to_file = new FileOutputStream(args[1]); + else to_file = System.out; + + // Now use the URL class to parse the user-specified URL into + // its various parts. + URL url = new URL(args[0]); + String protocol = url.getProtocol(); + if (!protocol.equals("http")) // Check that we support the protocol + throw new IllegalArgumentException("Must use 'http:' protocol"); + String host = url.getHost(); + int port = url.getPort(); + if (port == -1) port = 80; // if no port, use the default HTTP port + String filename = url.getFile(); + + // Open a network socket connection to the specified host and port + Socket socket = new Socket(host, port); + + // Get input and output streams for the socket + InputStream from_server = socket.getInputStream(); + PrintWriter to_server = new PrintWriter(socket.getOutputStream()); + + // Send the HTTP GET command to the Web server, specifying the file + // This uses an old and very simple version of the HTTP protocol + to_server.print("GET " + filename + "\n\n"); + to_server.flush(); // Send it right now! + + // Now read the server's response, and write it to the file + byte[] buffer = new byte[4096]; + int bytes_read; + while((bytes_read = from_server.read(buffer)) != -1) + to_file.write(buffer, 0, bytes_read); + + // When the server closes the connection, we close our stuff + socket.close(); + to_file.close(); + } + catch (Exception e) { // Report any errors that arise + System.err.println(e); + System.err.println("Usage: java HttpClient []"); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/HttpMirror.java b/src/main/java/com/davidflanagan/examples/net/HttpMirror.java new file mode 100644 index 0000000..4578b60 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/HttpMirror.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; + +/** + * This program is a very simple Web server. When it receives a HTTP request + * it sends the request back as the reply. This can be of interest when + * you want to see just what a Web client is requesting, or what data is + * being sent when a form is submitted, for example. + **/ +public class HttpMirror { + public static void main(String args[]) { + try { + // Get the port to listen on + int port = Integer.parseInt(args[0]); + // Create a ServerSocket to listen on that port. + ServerSocket ss = new ServerSocket(port); + // Now enter an infinite loop, waiting for & handling connections. + for(;;) { + + // Wait for a client to connect. The method will block; + // when it returns the socket will be connected to the client + Socket client = ss.accept(); + + // Get input and output streams to talk to the client + BufferedReader in = new BufferedReader( + new InputStreamReader(client.getInputStream())); + PrintWriter out = new PrintWriter(client.getOutputStream()); + + // Start sending our reply, using the HTTP 1.0 protocol + out.print("HTTP/1.0 200 \n"); // Version & status code + out.print("Content-Type: text/plain\n"); // The type of data + out.print("\n"); // End of headers + + // Now, read the HTTP request from the client, and send it + // right back to the client as part of the body of our + // response. The client doesn't disconnect, so we never get + // an EOF. It does sends an empty line at the end of the + // headers, though. So when we see the empty line, we stop + // reading. This means we don't mirror the contents of POST + // requests, for example. Note that the readLine() method + // works with Unix, Windows, and Mac line terminators. + String line; + while((line = in.readLine()) != null) { + if (line.length() == 0) break; + out.print(line + "\n"); + } + + // Close socket, breaking the connection to the client, and + // closing the input and output streams + out.close(); // Flush and close the output stream + in.close(); // Close the input stream + client.close(); // Close the socket itself + } // Now loop again, waiting for the next connection + } + // If anything goes wrong, print an error message + catch (Exception e) { + System.err.println(e); + System.err.println("Usage: java HttpMirror "); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/ProxyServer.java b/src/main/java/com/davidflanagan/examples/net/ProxyServer.java new file mode 100644 index 0000000..2dcec44 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/ProxyServer.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; + +/** + * This class uses the Server class to provide a multi-threaded server + * framework for a relatively simple proxy service. The main() method + * starts up the server. The nested Proxy class implements the + * Server.Service interface and provides the proxy service. + **/ +public class ProxyServer { + /** + * Create a Server object, and add Proxy service objects to it to provide + * proxy service as specified by the command-line arguments. + **/ + public static void main(String[] args) { + try { + // Check number of args. Must be a multiple of 3 and > 0. + if ((args.length == 0) || (args.length % 3 != 0)) + throw new IllegalArgumentException("Wrong number of args"); + + // Create the Server object + Server s = new Server(null, 12); // log stream, max connections + + // Loop through the arguments parsing (host, remoteport, localport) + // tuples. For each, create a Proxy, and add it to the server. + int i = 0; + while(i < args.length) { + String host = args[i++]; + int remoteport = Integer.parseInt(args[i++]); + int localport = Integer.parseInt(args[i++]); + s.addService(new Proxy(host, remoteport), localport); + } + } + catch (Exception e) { // Print an error message if anything goes wrong + System.err.println(e); + System.err.println("Usage: java ProxyServer " + + " ..."); + System.exit(1); + } + } + + /** + * This is the class that implements the proxy service. The serve() method + * will be called when the client has connected. At that point, it must + * establish a connection to the server, and then transfer bytes back and + * forth between client and server. For symmetry, this class implements + * two very similar threads as anonymous classes. One thread copies bytes + * from client to server, and the other copies them from server to client. + * The thread that invoke the serve() method creates and starts these + * threads, then just sits and waits for them to exit. + **/ + public static class Proxy implements Server.Service { + String host; + int port; + + /** Remember the host and port we are a proxy for */ + public Proxy(String host, int port) { + this.host = host; + this.port = port; + } + + /** The server invokes this method when a client connects. */ + public void serve(InputStream in, OutputStream out) { + // These are some sockets we'll use. They are final so they can + // be used by the anonymous classes defined below. + final InputStream from_client = in; + final OutputStream to_client = out; + final InputStream from_server; + final OutputStream to_server; + + // Try to establish a connection to the specified server and port + // and get sockets to talk to it. Tell our client if we fail. + final Socket server; + try { + server = new Socket(host, port); + from_server = server.getInputStream(); + to_server = server.getOutputStream(); + } + catch (Exception e) { + PrintWriter pw = new PrintWriter(new OutputStreamWriter(out)); + pw.print("Proxy server could not connect to " + host + + ":" + port + "\n"); + pw.flush(); + pw.close(); + try { in.close(); } catch (IOException ex) {} + return; + } + + // Create an array to hold two Threads. It is declared final so + // that it can be used by the anonymous classes below. We use an + // array instead of two variables because given the structure of + // this program two variables would not work if declared final. + final Thread[] threads = new Thread[2]; + + // Define and create a thread to copy bytes from client to server + Thread c2s = new Thread() { + public void run() { + // Copy bytes 'till EOF from client + byte[] buffer = new byte[2048]; + int bytes_read; + try { + while((bytes_read=from_client.read(buffer))!=-1) { + to_server.write(buffer, 0, bytes_read); + to_server.flush(); + } + } + catch (IOException e) {} + finally { + // When the thread is done + try { + server.close(); // close the server socket + to_client.close(); // and the client streams + from_client.close(); + } + catch (IOException e) {} + } + } + }; + + // Define and create a thread to copy bytes from server to client. + // This thread works just like the one above. + Thread s2c = new Thread() { + public void run() { + byte[] buffer = new byte[2048]; + int bytes_read; + try { + while((bytes_read=from_server.read(buffer))!=-1) { + to_client.write(buffer, 0, bytes_read); + to_client.flush(); + } + } + catch (IOException e) {} + finally { + try { + server.close(); // close down + to_client.close(); + from_client.close(); + } catch (IOException e) {} + } + } + }; + + // Store the threads into the final threads[] array, so that the + // anonymous classes can refer to each other. + threads[0] = c2s; threads[1] = s2c; + + // start the threads + c2s.start(); s2c.start(); + + // Wait for them to exit + try { c2s.join(); s2c.join(); } catch (InterruptedException e) {} + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/SendMail.java b/src/main/java/com/davidflanagan/examples/net/SendMail.java new file mode 100644 index 0000000..d5d5c81 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/SendMail.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; + +/** + * This program sends e-mail using a mailto: URL + **/ +public class SendMail { + public static void main(String[] args) { + try { + // If the user specified a mailhost, tell the system about it. + if (args.length >= 1) + System.getProperties().put("mail.host", args[0]); + + // A Reader stream to read from the console + BufferedReader in = + new BufferedReader(new InputStreamReader(System.in)); + + // Ask the user for the from, to, and subject lines + System.out.print("From: "); + String from = in.readLine(); + System.out.print("To: "); + String to = in.readLine(); + System.out.print("Subject: "); + String subject = in.readLine(); + + // Establish a network connection for sending mail + URL u = new URL("mailto:" + to); // Create a mailto: URL + URLConnection c = u.openConnection(); // Create its URLConnection + c.setDoInput(false); // Specify no input from it + c.setDoOutput(true); // Specify we'll do output + System.out.println("Connecting..."); // Tell the user + System.out.flush(); // Tell them right now + c.connect(); // Connect to mail host + PrintWriter out = // Get output stream to host + new PrintWriter(new OutputStreamWriter(c.getOutputStream())); + + // Write out mail headers. Don't let users fake the From address + out.print("From: \"" + from + "\" <" + + System.getProperty("user.name") + "@" + + InetAddress.getLocalHost().getHostName() + ">\n"); + out.print("To: " + to + "\n"); + out.print("Subject: " + subject + "\n"); + out.print("\n"); // blank line to end the list of headers + + // Now ask the user to enter the body of the message + System.out.println("Enter the message. " + + "End with a '.' on a line by itself."); + // Read message line by line and send it out. + String line; + for(;;) { + line = in.readLine(); + if ((line == null) || line.equals(".")) break; + out.print(line + "\n"); + } + + // Close (and flush) the stream to terminate the message + out.close(); + // Tell the user it was successfully sent. + System.out.println("Message sent."); + } + catch (Exception e) { // Handle any exceptions, print error message. + System.err.println(e); + System.err.println("Usage: java SendMail []"); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/Server.java b/src/main/java/com/davidflanagan/examples/net/Server.java new file mode 100644 index 0000000..2be26d3 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/Server.java @@ -0,0 +1,604 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * This class is a generic framework for a flexible, multi-threaded server. + * It listens on any number of specified ports, and, when it receives a + * connection on a port, passes input and output streams to a specified Service + * object which provides the actual service. It can limit the number of + * concurrent connections, and logs activity to a specified stream. + **/ +public class Server { + /** + * A main() method for running the server as a standalone program. The + * command-line arguments to the program should be pairs of servicenames + * and port numbers. For each pair, the program will dynamically load the + * named Service class, instantiate it, and tell the server to provide + * that Service on the specified port. The special -control argument + * should be followed by a password and port, and will start special + * server control service running on the specified port, protected by the + * specified password. + **/ + public static void main(String[] args) { + try { + if (args.length < 2) // Check number of arguments + throw new IllegalArgumentException("Must specify a service"); + + // Create a Server object that uses standard out as its log and + // has a limit of ten concurrent connections at once. + Server s = new Server(System.out, 10); + + // Parse the argument list + int i = 0; + while(i < args.length) { + if (args[i].equals("-control")) { // Handle the -control arg + i++; + String password = args[i++]; + int port = Integer.parseInt(args[i++]); + // add control service + s.addService(new Control(s, password), port); + } + else { + // Otherwise start a named service on the specified port. + // Dynamically load and instantiate a Service class + String serviceName = args[i++]; + Class serviceClass = Class.forName(serviceName); + Service service = (Service)serviceClass.newInstance(); + int port = Integer.parseInt(args[i++]); + s.addService(service, port); + } + } + } + catch (Exception e) { // Display a message if anything goes wrong + System.err.println("Server: " + e); + System.err.println("Usage: java Server " + + "[-control ] " + + "[ ... ]"); + System.exit(1); + } + } + + // This is the state for the server + Map services; // Hashtable mapping ports to Listeners + Set connections; // The set of current connections + int maxConnections; // The concurrent connection limit + ThreadGroup threadGroup; // The threadgroup for all our threads + PrintWriter logStream; // Where we send our logging output to + + /** + * This is the Server() constructor. It must be passed a stream + * to send log output to (may be null), and the limit on the number of + * concurrent connections. + **/ + public Server(OutputStream logStream, int maxConnections) { + setLogStream(logStream); + log("Starting server"); + threadGroup = new ThreadGroup(Server.class.getName()); + this.maxConnections = maxConnections; + services = new HashMap(); + connections = new HashSet(maxConnections); + } + + /** + * A public method to set the current logging stream. Pass null + * to turn logging off + **/ + public synchronized void setLogStream(OutputStream out) { + if (out != null) logStream = new PrintWriter(out); + else logStream = null; + } + + /** Write the specified string to the log */ + protected synchronized void log(String s) { + if (logStream != null) { + logStream.println("[" + new Date() + "] " + s); + logStream.flush(); + } + } + /** Write the specified object to the log */ + protected void log(Object o) { log(o.toString()); } + + /** + * This method makes the server start providing a new service. + * It runs the specified Service object on the specified port. + **/ + public synchronized void addService(Service service, int port) + throws IOException + { + Integer key = new Integer(port); // the hashtable key + // Check whether a service is already on that port + if (services.get(key) != null) + throw new IllegalArgumentException("Port " + port + + " already in use."); + // Create a Listener object to listen for connections on the port + Listener listener = new Listener(threadGroup, port, service); + // Store it in the hashtable + services.put(key, listener); + // Log it + log("Starting service " + service.getClass().getName() + + " on port " + port); + // Start the listener running. + listener.start(); + } + + /** + * This method makes the server stop providing a service on a port. + * It does not terminate any pending connections to that service, merely + * causes the server to stop accepting new connections + **/ + public synchronized void removeService(int port) { + Integer key = new Integer(port); // hashtable key + // Look up the Listener object for the port in the hashtable + final Listener listener = (Listener) services.get(key); + if (listener == null) return; + // Ask the listener to stop + listener.pleaseStop(); + // Remove it from the hashtable + services.remove(key); + // And log it. + log("Stopping service " + listener.service.getClass().getName() + + " on port " + port); + } + + /** + * This nested Thread subclass is a "listener". It listens for + * connections on a specified port (using a ServerSocket) and when it gets + * a connection request, it calls the servers addConnection() method to + * accept (or reject) the connection. There is one Listener for each + * Service being provided by the Server. + **/ + public class Listener extends Thread { + ServerSocket listen_socket; // The socket to listen for connections + int port; // The port we're listening on + Service service; // The service to provide on that port + volatile boolean stop = false; // Whether we've been asked to stop + + /** + * The Listener constructor creates a thread for itself in the + * threadgroup. It creates a ServerSocket to listen for connections + * on the specified port. It arranges for the ServerSocket to be + * interruptible, so that services can be removed from the server. + **/ + public Listener(ThreadGroup group, int port, Service service) + throws IOException + { + super(group, "Listener:" + port); + listen_socket = new ServerSocket(port); + // give it a non-zero timeout so accept() can be interrupted + listen_socket.setSoTimeout(600000); + this.port = port; + this.service = service; + } + + /** + * This is the polite way to get a Listener to stop accepting + * connections + ***/ + public void pleaseStop() { + this.stop = true; // Set the stop flag + this.interrupt(); // Stop blocking in accept() + try { listen_socket.close(); } // Stop listening. + catch(IOException e) {} + } + + /** + * A Listener is a Thread, and this is its body. + * Wait for connection requests, accept them, and pass the socket on + * to the addConnection method of the server. + **/ + public void run() { + while(!stop) { // loop until we're asked to stop. + try { + Socket client = listen_socket.accept(); + addConnection(client, service); + } + catch (InterruptedIOException e) {} + catch (IOException e) {log(e);} + } + } + } + + /** + * This is the method that Listener objects call when they accept a + * connection from a client. It either creates a Connection object + * for the connection and adds it to the list of current connections, + * or, if the limit on connections has been reached, it closes the + * connection. + **/ + protected synchronized void addConnection(Socket s, Service service) { + // If the connection limit has been reached + if (connections.size() >= maxConnections) { + try { + // Then tell the client it is being rejected. + PrintWriter out = new PrintWriter(s.getOutputStream()); + out.print("Connection refused; " + + "the server is busy; please try again later.\n"); + out.flush(); + // And close the connection to the rejected client. + s.close(); + // And log it, of course + log("Connection refused to " + + s.getInetAddress().getHostAddress() + + ":" + s.getPort() + ": max connections reached."); + } catch (IOException e) {log(e);} + } + else { // Otherwise, if the limit has not been reached + // Create a Connection thread to handle this connection + Connection c = new Connection(s, service); + // Add it to the list of current connections + connections.add(c); + // Log this new connection + log("Connected to " + s.getInetAddress().getHostAddress() + + ":" + s.getPort() + " on port " + s.getLocalPort() + + " for service " + service.getClass().getName()); + // And start the Connection thread to provide the service + c.start(); + } + } + + /** + * A Connection thread calls this method just before it exits. It removes + * the specified Connection from the set of connections. + **/ + protected synchronized void endConnection(Connection c) { + connections.remove(c); + log("Connection to " + c.client.getInetAddress().getHostAddress() + + ":" + c.client.getPort() + " closed."); + } + + /** Change the current connection limit */ + public synchronized void setMaxConnections(int max) { + maxConnections = max; + } + + /** + * This method displays status information about the server on the + * specified stream. It can be used for debugging, and is used by the + * Control service later in this example. + **/ + public synchronized void displayStatus(PrintWriter out) { + // Display a list of all Services that are being provided + Iterator keys = services.keySet().iterator(); + while(keys.hasNext()) { + Integer port = (Integer) keys.next(); + Listener listener = (Listener) services.get(port); + out.print("SERVICE " + listener.service.getClass().getName() + + " ON PORT " + port + "\n"); + } + + // Display the current connection limit + out.print("MAX CONNECTIONS: " + maxConnections + "\n"); + + // Display a list of all current connections + Iterator conns = connections.iterator(); + while(conns.hasNext()) { + Connection c = (Connection)conns.next(); + out.print("CONNECTED TO " + + c.client.getInetAddress().getHostAddress() + + ":" + c.client.getPort() + " ON PORT " + + c.client.getLocalPort() + " FOR SERVICE " + + c.service.getClass().getName() + "\n"); + } + } + + /** + * This class is a subclass of Thread that handles an individual + * connection between a client and a Service provided by this server. + * Because each such connection has a thread of its own, each Service can + * have multiple connections pending at once. Despite all the other + * threads in use, this is the key feature that makes this a + * multi-threaded server implementation. + **/ + public class Connection extends Thread { + Socket client; // The socket to talk to the client through + Service service; // The service being provided to that client + + /** + * This constructor just saves some state and calls the superclass + * constructor to create a thread to handle the connection. Connection + * objects are created by Listener threads. These threads are part of + * the server's ThreadGroup, so all Connection threads are part of that + * group, too. + **/ + public Connection(Socket client, Service service) { + super("Server.Connection:" + + client.getInetAddress().getHostAddress() + + ":" + client.getPort()); + this.client = client; + this.service = service; + } + + /** + * This is the body of each and every Connection thread. + * All it does is pass the client input and output streams to the + * serve() method of the specified Service object. That method is + * responsible for reading from and writing to those streams to + * provide the actual service. Recall that the Service object has + * been passed from the Server.addService() method to a Listener + * object to the addConnection() method to this Connection object, and + * is now finally being used to provide the service. Note that just + * before this thread exits it always calls the endConnection() method + * to remove itself from the set of connections + **/ + public void run() { + try { + InputStream in = client.getInputStream(); + OutputStream out = client.getOutputStream(); + service.serve(in, out); + } + catch (IOException e) {log(e);} + finally { endConnection(this); } + } + } + + /** + * Here is the Service interface that we have seen so much of. It defines + * only a single method which is invoked to provide the service. serve() + * will be passed an input stream and an output stream to the client. It + * should do whatever it wants with them, and should close them before + * returning. + * + * All connections through the same port to this service share a single + * Service object. Thus, any state local to an individual connection must + * be stored in local variables within the serve() method. State that + * should be global to all connections on the same port should be stored + * in instance variables of the Service class. If the same Service is + * running on more than one port, there will typically be different + * Service instances for each port. Data that should be global to all + * connections on any port should be stored in static variables. + * + * Note that implementations of this interface must have a no-argument + * constructor if they are to be dynamically instantiated by the main() + * method of the Server class. + **/ + public interface Service { + public void serve(InputStream in, OutputStream out) throws IOException; + } + + /** + * A very simple service. It displays the current time on the server + * to the client, and closes the connection. + **/ + public static class Time implements Service { + public void serve(InputStream i, OutputStream o) throws IOException { + PrintWriter out = new PrintWriter(o); + out.print(new Date() + "\n"); + out.close(); + i.close(); + } + } + + /** + * This is another example service. It reads lines of input from the + * client, and sends them back, reversed. It also displays a welcome + * message and instructions, and closes the connection when the user + * enters a '.' on a line by itself. + **/ + public static class Reverse implements Service { + public void serve(InputStream i, OutputStream o) throws IOException { + BufferedReader in = new BufferedReader(new InputStreamReader(i)); + PrintWriter out = + new PrintWriter(new BufferedWriter(new OutputStreamWriter(o))); + out.print("Welcome to the line reversal server.\n"); + out.print("Enter lines. End with a '.' on a line by itself.\n"); + for(;;) { + out.print("> "); + out.flush(); + String line = in.readLine(); + if ((line == null) || line.equals(".")) break; + for(int j = line.length()-1; j >= 0; j--) + out.print(line.charAt(j)); + out.print("\n"); + } + out.close(); + in.close(); + } + } + + /** + * This service is an HTTP mirror, just like the HttpMirror class + * implemented earlier in this chapter. It echos back the client's + * HTTP request + **/ + public static class HTTPMirror implements Service { + public void serve(InputStream i, OutputStream o) throws IOException { + BufferedReader in = new BufferedReader(new InputStreamReader(i)); + PrintWriter out = new PrintWriter(o); + out.print("HTTP/1.0 200 \n"); + out.print("Content-Type: text/plain\n\n"); + String line; + while((line = in.readLine()) != null) { + if (line.length() == 0) break; + out.print(line + "\n"); + } + out.close(); + in.close(); + } + } + + /** + * This service demonstrates how to maintain state across connections by + * saving it in instance variables and using synchronized access to those + * variables. It maintains a count of how many clients have connected and + * tells each client what number it is + **/ + public static class UniqueID implements Service { + public int id=0; + public synchronized int nextId() { return id++; } + public void serve(InputStream i, OutputStream o) throws IOException { + PrintWriter out = new PrintWriter(o); + out.print("You are client #: " + nextId() + "\n"); + out.close(); + i.close(); + } + } + + /** + * This is a non-trivial service. It implements a command-based protocol + * that gives password-protected runtime control over the operation of the + * server. See the main() method of the Server class to see how this + * service is started. + * + * The recognized commands are: + * password: give password; authorization is required for most commands + * add: dynamically add a named service on a specified port + * remove: dynamically remove the service running on a specified port + * max: change the current maximum connection limit. + * status: display current services, connections, and connection limit + * help: display a help message + * quit: disconnect + * + * This service displays a prompt, and sends all of its output to the user + * in capital letters. Only one client is allowed to connect to this + * service at a time. + **/ + public static class Control implements Service { + Server server; // The server we control + String password; // The password we require + boolean connected = false; // Whether a client is already connected + + /** + * Create a new Control service. It will control the specified Server + * object, and will require the specified password for authorization + * Note that this Service does not have a no argument constructor, + * which means that it cannot be dynamically instantiated and added as + * the other, generic services above can be. + **/ + public Control(Server server, String password) { + this.server = server; + this.password = password; + } + + /** + * This is the serve method that provides the service. It reads a + * line the client, and uses java.util.StringTokenizer to parse it + * into commands and arguments. It does various things depending on + * the command. + **/ + public void serve(InputStream i, OutputStream o) throws IOException { + // Setup the streams + BufferedReader in = new BufferedReader(new InputStreamReader(i)); + PrintWriter out = new PrintWriter(o); + String line; // For reading client input lines + // Has the user has given the password yet? + boolean authorized = false; + + // If there is already a client connected to this service, display + // a message to this client and close the connection. We use a + // synchronized block to prevent a race condition. + synchronized(this) { + if (connected) { + out.print("ONLY ONE CONTROL CONNECTION ALLOWED.\n"); + out.close(); + return; + } + else connected = true; + } + + // This is the main loop: read a command, parse it, and handle it + for(;;) { // infinite loop + out.print("> "); // Display a prompt + out.flush(); // Make it appear right away + line = in.readLine(); // Get the user's input + if (line == null) break; // Quit if we get EOF. + try { + // Use a StringTokenizer to parse the user's command + StringTokenizer t = new StringTokenizer(line); + if (!t.hasMoreTokens()) continue; // if input was empty + // Get first word of the input and convert to lower case + String command = t.nextToken().toLowerCase(); + // Now compare to each of the possible commands, doing the + // appropriate thing for each command + if (command.equals("password")) { // Password command + String p = t.nextToken(); // Get the next word + if (p.equals(this.password)) { // Is it the password? + out.print("OK\n"); // Say so + authorized = true; // Grant authorization + } + else out.print("INVALID PASSWORD\n"); // Otherwise fail + } + else if (command.equals("add")) { // Add Service command + // Check whether password has been given + if (!authorized) out.print("PASSWORD REQUIRED\n"); + else { + // Get the name of the service and try to + // dynamically load and instantiate it. + // Exceptions will be handled below + String serviceName = t.nextToken(); + Class serviceClass = Class.forName(serviceName); + Service service; + try { + service = (Service)serviceClass.newInstance(); + } + catch (NoSuchMethodError e) { + throw new IllegalArgumentException( + "Service must have a " + + "no-argument constructor"); + } + int port = Integer.parseInt(t.nextToken()); + // If no exceptions occurred, add the service + server.addService(service, port); + out.print("SERVICE ADDED\n"); // acknowledge + } + } + else if (command.equals("remove")) { // Remove service + if (!authorized) out.print("PASSWORD REQUIRED\n"); + else { + int port = Integer.parseInt(t.nextToken()); + server.removeService(port); // remove the service + out.print("SERVICE REMOVED\n"); // acknowledge + } + } + else if (command.equals("max")) { // Set connection limit + if (!authorized) out.print("PASSWORD REQUIRED\n"); + else { + int max = Integer.parseInt(t.nextToken()); + server.setMaxConnections(max); + out.print("MAX CONNECTIONS CHANGED\n"); + } + } + else if (command.equals("status")) { // Status Display + if (!authorized) out.print("PASSWORD REQUIRED\n"); + else server.displayStatus(out); + } + else if (command.equals("help")) { // Help command + // Display command syntax. Password not required + out.print("COMMANDS:\n" + + "\tpassword \n" + + "\tadd \n" + + "\tremove \n" + + "\tmax \n" + + "\tstatus\n" + + "\thelp\n" + + "\tquit\n"); + } + else if (command.equals("quit")) break; // Quit command. + else out.print("UNRECOGNIZED COMMAND\n"); // Error + } + catch (Exception e) { + // If an exception occurred during the command, print an + // error message, then output details of the exception. + out.print("ERROR WHILE PARSING OR EXECUTING COMMAND:\n" + + e + "\n"); + } + } + // Finally, when the loop command loop ends, close the streams + // and set our connected flag to false so that other clients can + // now connect. + connected = false; + out.close(); + in.close(); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/SimpleProxyServer.java b/src/main/java/com/davidflanagan/examples/net/SimpleProxyServer.java new file mode 100644 index 0000000..f987b74 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/SimpleProxyServer.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; + +/** + * This class implements a simple single-threaded proxy server. + **/ +public class SimpleProxyServer { + /** The main method parses arguments and passes them to runServer */ + public static void main(String[] args) throws IOException { + try { + // Check the number of arguments + if (args.length != 3) + throw new IllegalArgumentException("Wrong number of args."); + + // Get the command-line arguments: the host and port we are proxy + // for and the local port that we listen for connections on. + String host = args[0]; + int remoteport = Integer.parseInt(args[1]); + int localport = Integer.parseInt(args[2]); + // Print a start-up message + System.out.println("Starting proxy for " + host + ":" + + remoteport + " on port " + localport); + // And start running the server + runServer(host, remoteport, localport); // never returns + } + catch (Exception e) { + System.err.println(e); + System.err.println("Usage: java SimpleProxyServer " + + " "); + } + } + + /** + * This method runs a single-threaded proxy server for + * host:remoteport on the specified local port. It never returns. + **/ + public static void runServer(String host, int remoteport, int localport) + throws IOException { + // Create a ServerSocket to listen for connections with + ServerSocket ss = new ServerSocket(localport); + + // Create buffers for client-to-server and server-to-client transfer. + // We make one final so it can be used in an anonymous class below. + // Note the assumptions about the volume of traffic in each direction. + final byte[] request = new byte[1024]; + byte[] reply = new byte[4096]; + + // This is a server that never returns, so enter an infinite loop. + while(true) { + // Variables to hold the sockets to the client and to the server. + Socket client = null, server = null; + try { + // Wait for a connection on the local port + client = ss.accept(); + + // Get client streams. Make them final so they can + // be used in the anonymous thread below. + final InputStream from_client = client.getInputStream(); + final OutputStream to_client = client.getOutputStream(); + + // Make a connection to the real server. + // If we cannot connect to the server, send an error to the + // client, disconnect, and continue waiting for connections. + try { server = new Socket(host, remoteport); } + catch (IOException e) { + PrintWriter out = new PrintWriter(to_client); + out.print("Proxy server cannot connect to " + host + ":"+ + remoteport + ":\n" + e + "\n"); + out.flush(); + client.close(); + continue; + } + + // Get server streams. + final InputStream from_server = server.getInputStream(); + final OutputStream to_server = server.getOutputStream(); + + // Make a thread to read the client's requests and pass them + // to the server. We have to use a separate thread because + // requests and responses may be asynchronous. + Thread t = new Thread() { + public void run() { + int bytes_read; + try { + while((bytes_read=from_client.read(request))!=-1) { + to_server.write(request, 0, bytes_read); + to_server.flush(); + } + } + catch (IOException e) {} + + // the client closed the connection to us, so close our + // connection to the server. This will also cause the + // server-to-client loop in the main thread exit. + try {to_server.close();} catch (IOException e) {} + } + }; + + // Start the client-to-server request thread running + t.start(); + + // Meanwhile, in the main thread, read the server's responses + // and pass them back to the client. This will be done in + // parallel with the client-to-server request thread above. + int bytes_read; + try { + while((bytes_read = from_server.read(reply)) != -1) { + to_client.write(reply, 0, bytes_read); + to_client.flush(); + } + } + catch(IOException e) {} + + // The server closed its connection to us, so we close our + // connection to our client. + // This will make the other thread exit. + to_client.close(); + } + catch (IOException e) { System.err.println(e); } + finally { // Close the sockets no matter what happens. + try { + if (server != null) server.close(); + if (client != null) client.close(); + } + catch(IOException e) {} + } + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/UDPReceive.java b/src/main/java/com/davidflanagan/examples/net/UDPReceive.java new file mode 100644 index 0000000..9df4043 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/UDPReceive.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; + +/** + * This program waits to receive datagrams sent the specified port. + * When it receives one, it displays the sending host and prints the + * contents of the datagram as a string. Then it loops and waits again. + **/ +public class UDPReceive { + public static final String usage = "Usage: java UDPReceive "; + public static void main(String args[]) { + try { + if (args.length != 1) + throw new IllegalArgumentException("Wrong number of args"); + + // Get the port from the command line + int port = Integer.parseInt(args[0]); + + // Create a socket to listen on the port. + DatagramSocket dsocket = new DatagramSocket(port); + + // Create a buffer to read datagrams into. If anyone sends us a + // packet containing more than will fit into this buffer, the + // excess will simply be discarded! + byte[] buffer = new byte[2048]; + + // Create a packet to receive data into the buffer + DatagramPacket packet = new DatagramPacket(buffer, buffer.length); + + // Now loop forever, waiting to receive packets and printing them. + for(;;) { + // Wait to receive a datagram + dsocket.receive(packet); + + // Convert the contents to a string, and display them + String msg = new String(buffer, 0, packet.getLength()); + System.out.println(packet.getAddress().getHostName() + + ": " + msg); + + // Reset the length of the packet before reusing it. + // Prior to Java 1.1, we'd just create a new packet each time. + packet.setLength(buffer.length); + } + } + catch (Exception e) { + System.err.println(e); + System.err.println(usage); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/UDPSend.java b/src/main/java/com/davidflanagan/examples/net/UDPSend.java new file mode 100644 index 0000000..9b021e6 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/UDPSend.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.io.*; +import java.net.*; + +/** + * This class sends the specified text or file as a datagram to the + * specified port of the specified host. + **/ +public class UDPSend { + public static final String usage = + "Usage: java UDPSend ...\n" + + " or: java UDPSend -f "; + + public static void main(String args[]) { + try { + // Check the number of arguments + if (args.length < 3) + throw new IllegalArgumentException("Wrong number of args"); + + // Parse the arguments + String host = args[0]; + int port = Integer.parseInt(args[1]); + + // Figure out the message to send. + // If the third argument is -f, then send the contents of the file + // specified as the fourth argument. Otherwise, concatenate the + // third and all remaining arguments and send that. + byte[] message; + if (args[2].equals("-f")) { + File f = new File(args[3]); + int len = (int)f.length(); // figure out how big the file is + message = new byte[len]; // create a buffer big enough + FileInputStream in = new FileInputStream(f); + int bytes_read = 0, n; + do { // loop until we've read it all + n = in.read(message, bytes_read, len-bytes_read); + bytes_read += n; + } while((bytes_read < len)&& (n != -1)); + } + else { // Otherwise, just combine all the remaining arguments. + String msg = args[2]; + for (int i = 3; i < args.length; i++) msg += " " + args[i]; + message = msg.getBytes(); + } + + // Get the internet address of the specified host + InetAddress address = InetAddress.getByName(host); + + // Initialize a datagram packet with data and address + DatagramPacket packet = new DatagramPacket(message, message.length, + address, port); + + // Create a datagram socket, send the packet through it, close it. + DatagramSocket dsocket = new DatagramSocket(); + dsocket.send(packet); + dsocket.close(); + } + catch (Exception e) { + System.err.println(e); + System.err.println(usage); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/net/Who.html b/src/main/java/com/davidflanagan/examples/net/Who.html new file mode 100644 index 0000000..daec603 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/Who.html @@ -0,0 +1,4 @@ + + diff --git a/src/main/java/com/davidflanagan/examples/net/Who.java b/src/main/java/com/davidflanagan/examples/net/Who.java new file mode 100644 index 0000000..8fff7af --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/net/Who.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.net; +import java.applet.*; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.net.*; + +/** + * This applet connects to the "finger" server on the host + * it was served from to determine who is currently logged on. + * Because it is an untrusted applet, it can only connect to the host + * from which it came. Since web servers do not usually run finger + * servers themselves, this applet will often be used in conjunction + * with a proxy server, to serve it from some other host that does run + * a finger server. + **/ +public class Who extends Applet implements ActionListener, Runnable { + Button who; // The button in the applet + + /** + * The init method just creates a button to display in the applet. + * When the user clicks the button, we'll check who is logged on. + **/ + public void init() { + who = new Button("Who?"); + who.setFont(new Font("SansSerif", Font.PLAIN, 14)); + who.addActionListener(this); + this.add(who); + } + + /** + * When the button is clicked, start a thread that will connect to + * the finger server and display who is logged on + **/ + public void actionPerformed(ActionEvent e) { new Thread(this).start(); } + + /** + * This is the method that does the networking and displays the results. + * It is implemented as the body of a separate thread because it might + * take some time to complete, and applet methods need to return promptly. + **/ + public void run() { + // Disable the button so we don't get multiple queries at once... + who.setEnabled(false); + + // Create a window to display the output in + Frame f = new Frame("Who's Logged On: Connecting..."); + f.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + ((Frame)e.getSource()).dispose(); + } + }); + TextArea t = new TextArea(10, 80); + t.setFont(new Font("MonoSpaced", Font.PLAIN, 10)); + f.add(t, "Center"); + f.pack(); + f.show(); + + // Find out who's logged on + Socket s = null; + PrintWriter out = null; + BufferedReader in = null; + try { + // Connect to port 79 (the standard finger port) on the host + // that the applet was loaded from. + String hostname = this.getCodeBase().getHost(); + s = new Socket(hostname, 79); + // Set up the streams + out = new PrintWriter(new OutputStreamWriter(s.getOutputStream())); + in = new BufferedReader(new InputStreamReader(s.getInputStream())); + + // Send a blank line to the finger server, telling it that we want + // a listing of everyone logged on instead of information about an + // individual user. + out.print("\n"); + out.flush(); // Send it now! + + // Now read the server's response and display it in the textarea + // The server should send lines terminated with \n. The + // readLine() method will detect these lines, even when running + // on a Mac that terminates lines with \r + String line; + while((line = in.readLine()) != null) { + t.append(line); + t.append("\n"); + } + // Update the window title to indicate we're finished + f.setTitle("Who's Logged On: " + hostname); + } + // If something goes wrong, we'll just display the exception message + catch (IOException e) { + t.append(e.toString()); + f.setTitle("Who's Logged On: Error"); + } + // And finally, don't forget to close the streams! + finally { + try { in.close(); out.close(); s.close(); } catch(Exception e) {} + } + + // And enable the button again + who.setEnabled(true); + } +} + + + diff --git a/src/main/java/com/davidflanagan/examples/print/HardcopyWriter.java b/src/main/java/com/davidflanagan/examples/print/HardcopyWriter.java new file mode 100644 index 0000000..3be2358 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/print/HardcopyWriter.java @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.print; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.text.*; +import java.util.*; + +/** + * A character output stream that sends output to a printer. + **/ +public class HardcopyWriter extends Writer { + // These are the instance variables for the class + protected PrintJob job; // The PrintJob object in use + protected Graphics page; // Graphics object for current page + protected String jobname; // The name of the print job + protected int fontsize; // Point size of the font + protected String time; // Current time (appears in header) + protected Dimension pagesize; // Size of the page (in dots) + protected int pagedpi; // Page resolution in dots per inch + protected Font font, headerfont; // Body font and header font + protected FontMetrics metrics; // Metrics for the body font + protected FontMetrics headermetrics; // Metrics for the header font + protected int x0, y0; // Upper-left corner inside margin + protected int width, height; // Size (in dots) inside margins + protected int headery; // Baseline of the page header + protected int charwidth; // The width of each character + protected int lineheight; // The height of each line + protected int lineascent; // Offset of font baseline + protected int chars_per_line; // Number of characters per line + protected int lines_per_page; // Number of lines per page + protected int charnum = 0, linenum = 0; // Current column and line position + protected int pagenum = 0; // Current page number + + // A field to save state between invocations of the write() method + private boolean last_char_was_return = false; + + // A static variable that holds user preferences between print jobs + protected static Properties printprops = new Properties(); + + /** + * The constructor for this class has a bunch of arguments: + * The frame argument is required for all printing in Java. + * The jobname appears left justified at the top of each printed page. + * The font size is specified in points, as on-screen font sizes are. + * The margins are specified in inches (or fractions of inches). + **/ + public HardcopyWriter(Frame frame, String jobname, int fontsize, + double leftmargin, double rightmargin, + double topmargin, double bottommargin) + throws PrintCanceledException + { + // Get the PrintJob object with which we'll do all the printing. + // The call is synchronized on the static printprops object, which + // means that only one print dialog can be popped up at a time. + // If the user clicks Cancel in the print dialog, throw an exception. + Toolkit toolkit = frame.getToolkit(); // get Toolkit from Frame + synchronized(printprops) { + job = toolkit.getPrintJob(frame, jobname, printprops); + } + if (job == null) + throw new PrintCanceledException("User cancelled print request"); + + pagesize = job.getPageDimension(); // query the page size + pagedpi = job.getPageResolution(); // query the page resolution + + // Bug Workaround: + // On windows, getPageDimension() and getPageResolution don't work, so + // we've got to fake them. + if (System.getProperty("os.name").regionMatches(true,0,"windows",0,7)){ + // Use screen dpi, which is what the PrintJob tries to emulate + pagedpi = toolkit.getScreenResolution(); + // Assume a 8.5" x 11" page size. A4 paper users must change this. + pagesize = new Dimension((int)(8.5 * pagedpi), 11*pagedpi); + // We also have to adjust the fontsize. It is specified in points, + // (1 point = 1/72 of an inch) but Windows measures it in pixels. + fontsize = fontsize * pagedpi / 72; + } + + // Compute coordinates of the upper-left corner of the page. + // I.e. the coordinates of (leftmargin, topmargin). Also compute + // the width and height inside of the margins. + x0 = (int)(leftmargin * pagedpi); + y0 = (int)(topmargin * pagedpi); + width = pagesize.width - (int)((leftmargin + rightmargin) * pagedpi); + height = pagesize.height - (int)((topmargin + bottommargin) * pagedpi); + + // Get body font and font size + font = new Font("Monospaced", Font.PLAIN, fontsize); + metrics = frame.getFontMetrics(font); + lineheight = metrics.getHeight(); + lineascent = metrics.getAscent(); + charwidth = metrics.charWidth('0'); // Assumes a monospaced font! + + // Now compute columns and lines will fit inside the margins + chars_per_line = width / charwidth; + lines_per_page = height / lineheight; + + // Get header font information + // And compute baseline of page header: 1/8" above the top margin + headerfont = new Font("SansSerif", Font.ITALIC, fontsize); + headermetrics = frame.getFontMetrics(headerfont); + headery = y0 - (int)(0.125 * pagedpi) - + headermetrics.getHeight() + headermetrics.getAscent(); + + // Compute the date/time string to display in the page header + DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, + DateFormat.SHORT); + df.setTimeZone(TimeZone.getDefault()); + time = df.format(new Date()); + + this.jobname = jobname; // save name + this.fontsize = fontsize; // save font size + } + + /** + * This is the write() method of the stream. All Writer subclasses + * implement this. All other versions of write() are variants of this one + **/ + public void write(char[] buffer, int index, int len) { + synchronized(this.lock) { // For thread safety + // Loop through all the characters passed to us + for(int i = index; i < index + len; i++) { + // If we haven't begun a page (or a new page), do that now. + if (page == null) newpage(); + + // If the character is a line terminator, then begin new line, + // unless it is a \n immediately after a \r. + if (buffer[i] == '\n') { + if (!last_char_was_return) newline(); + continue; + } + if (buffer[i] == '\r') { + newline(); + last_char_was_return = true; + continue; + } + else last_char_was_return = false; + + // If it some other non-printing character, ignore it. + if (Character.isWhitespace(buffer[i]) && + !Character.isSpaceChar(buffer[i]) && (buffer[i] != '\t')) + continue; + + // If no more characters will fit on the line, start new line. + if (charnum >= chars_per_line) { + newline(); + // Also start a new page, if necessary + if (page == null) newpage(); + } + + // Now print the character: + // If it is a space, skip one space, without output. + // If it is a tab, skip the necessary number of spaces. + // Otherwise, print the character. + // It is inefficient to draw only one character at a time, but + // because our FontMetrics don't match up exactly to what the + // printer uses we need to position each character individually + if (Character.isSpaceChar(buffer[i])) charnum++; + else if (buffer[i] == '\t') charnum += 8 - (charnum % 8); + else { + page.drawChars(buffer, i, 1, + x0 + charnum * charwidth, + y0 + (linenum*lineheight) + lineascent); + charnum++; + } + } + } + } + + /** + * This is the flush() method that all Writer subclasses must implement. + * There is no way to flush a PrintJob without prematurely printing the + * page, so we don't do anything. + **/ + public void flush() { /* do nothing */ } + + /** + * This is the close() method that all Writer subclasses must implement. + * Print the pending page (if any) and terminate the PrintJob. + */ + public void close() { + synchronized(this.lock) { + if (page != null) page.dispose(); // Send page to the printer + job.end(); // Terminate the job + } + } + + /** + * Set the font style. The argument should be one of the font style + * constants defined by the java.awt.Font class. All subsequent output + * will be in that style. This method relies on all styles of the + * Monospaced font having the same metrics. + **/ + public void setFontStyle(int style) { + synchronized (this.lock) { + // Try to set a new font, but restore current one if it fails + Font current = font; + try { font = new Font("Monospaced", style, fontsize); } + catch (Exception e) { font = current; } + // If a page is pending, set the new font. Otherwise newpage() will + if (page != null) page.setFont(font); + } + } + + /** End the current page. Subsequent output will be on a new page. */ + public void pageBreak() { synchronized(this.lock) { newpage(); } } + + /** Return the number of columns of characters that fit on the page */ + public int getCharactersPerLine() { return this.chars_per_line; } + + /** Return the number of lines that fit on a page */ + public int getLinesPerPage() { return this.lines_per_page; } + + /** This internal method begins a new line */ + protected void newline() { + charnum = 0; // Reset character number to 0 + linenum++; // Increment line number + if (linenum >= lines_per_page) { // If we've reached the end of page + page.dispose(); // send page to printer + page = null; // but don't start a new page yet. + } + } + + /** This internal method begins a new page and prints the header. */ + protected void newpage() { + page = job.getGraphics(); // Begin the new page + linenum = 0; charnum = 0; // Reset line and char number + pagenum++; // Increment page number + page.setFont(headerfont); // Set the header font. + page.drawString(jobname, x0, headery); // Print job name left justified + + String s = "- " + pagenum + " -"; // Print the page # centered. + int w = headermetrics.stringWidth(s); + page.drawString(s, x0 + (this.width - w)/2, headery); + w = headermetrics.stringWidth(time); // Print date right justified + page.drawString(time, x0 + width - w, headery); + + // Draw a line beneath the header + int y = headery + headermetrics.getDescent() + 1; + page.drawLine(x0, y, x0+width, y); + + // Set the basic monospaced font for the rest of the page. + page.setFont(font); + } + + /** + * This is the exception class that the HardcopyWriter constructor + * throws when the user clicks "Cancel" in the print dialog box. + **/ + public static class PrintCanceledException extends Exception { + public PrintCanceledException(String msg) { super(msg); } + } + + /** + * A program that prints the specified file using HardcopyWriter + **/ + public static class PrintFile { + public static void main(String[] args) { + try { + if (args.length != 1) + throw new IllegalArgumentException("Wrong # of arguments"); + FileReader in = new FileReader(args[0]); + HardcopyWriter out = null; + Frame f = new Frame("PrintFile: " + args[0]); + f.setSize(200, 50); + f.show(); + try { + out = new HardcopyWriter(f, args[0], 10, .5, .5, .5, .5); + } + catch (PrintCanceledException e) { + System.exit(0); + } + f.setVisible(false); + char[] buffer = new char[4096]; + int numchars; + while((numchars = in.read(buffer)) != -1) + out.write(buffer, 0, numchars); + in.close(); + out.close(); + } + catch (Exception e) { + System.err.println(e); + System.err.println("Usage: " + + "java HardcopyWriter$PrintFile "); + System.exit(1); + } + System.exit(0); + } + } + + /** + * A program that prints a demo page using HardcopyWriter + **/ + public static class Demo extends Frame implements ActionListener { + /** The main method of the program. Create a test window */ + public static void main(String[] args) { + Frame f = new Demo(); + f.show(); + } + // Buttons used in this program + protected Button print, quit; + + /** Constructor for the test program's window. */ + public Demo() { + super("HardcopyWriter Test"); // Call frame constructor + Panel p = new Panel(); // Add a panel to the frame + this.add(p, "Center"); // Center it + p.setFont(new Font("SansSerif", // Set a default font + Font.BOLD, 18)); + print = new Button("Print Test Page"); // Create a Print button + quit = new Button("Quit"); // Create a Quit button + print.addActionListener(this); // Specify that we'll handle + quit.addActionListener(this); // button presses + p.add(print); // Add the buttons to panel + p.add(quit); + this.pack(); // Set the frame size + } + + /** Handle the button presses */ + public void actionPerformed(ActionEvent e) { + Object o = e.getSource(); + if (o == quit) System.exit(0); + else if (o == print) printDemoPage(); + } + + /** Print the demo page */ + public void printDemoPage() { + // Create a HardcopyWriter, using a 14 point font and 3/4" margins. + HardcopyWriter hw; + try { hw=new HardcopyWriter(this, "Demo Page",14,.75,.75,.75,.75);} + catch (PrintCanceledException e) { return; } + + // Send output to it through a PrintWriter stream + PrintWriter out = new PrintWriter(hw); + + // Figure out the size of the page + int rows = hw.getLinesPerPage(), cols = hw.getCharactersPerLine(); + + // Mark upper left and upper-right corners + out.print("+"); // upper-left corner + for(int i=0;i,.?:;+-=/\\`'\"_~|"); + } + hw.setFontStyle(Font.PLAIN); + out.println("\n"); + + // Demonstrate tab stops + hw.setFontStyle(Font.BOLD); + out.println("Tab Stops:"); + hw.setFontStyle(Font.PLAIN); + out.println(" 1 2 3 4 5"); + out.println("012345678901234567890123456789012345678901234567890"); + out.println("^\t^\t^\t^\t^\t^\t^"); + out.println("\n"); + + // Output some information about page dimensions and resolution + hw.setFontStyle(Font.BOLD); + out.println("Dimensions:"); + hw.setFontStyle(Font.PLAIN); + out.println("\tResolution: " + hw.pagedpi + " dots per inch"); + out.println("\tPage width (pixels): " + hw.pagesize.width); + out.println("\tPage height (pixels): " + hw.pagesize.height); + out.println("\tWidth inside margins (pixels): " + hw.width); + out.println("\tHeight inside margins (pixels): " + hw.height); + out.println("\tCharacters per line: " + cols); + out.println("\tLines per page: " + rows); + + // Skip down to the bottom of the page + for(int i = 0; i < rows-30; i++) out.println(); + + // And mark the lower left and lower right + out.print("+"); // lower-left + for(int i=0;i printHeight) paginate(root, drawRect); + + // Once we've broken it into pages, figure out how many pages. + numPages = pageLengths.size() + 1; + } + + // This is the starting offset of the page we're currently working on + double pageStart = 0; + + /** + * This method loops through the children of the specified view, + * recursing as necessary, and inserts pages breaks when needed. + * It makes a rudimentary attempt to avoid "widows" and "orphans". + **/ + protected void paginate(View v, Rectangle2D allocation) { + // Figure out how tall this view is, and tell it to allocate + // that space among its children + double myheight = v.getPreferredSpan(View.Y_AXIS); + v.setSize((float)printWidth, (float)myheight); + + // Now loop through each of the children + int numkids = v.getViewCount(); + for(int i = 0; i < numkids; i++) { + View kid = v.getView(i); // this is the child we're working with + // Figure out its size and location + Shape kidshape = v.getChildAllocation(i, allocation); + if (kidshape == null) continue; + Rectangle2D kidbox = kidshape.getBounds2D(); + + // This is the Y coordinate of the bottom of the child + double kidpos = kidbox.getY() + kidbox.getHeight() - pageStart; + + // If this is the first child of a group, then we want to ensure + // that it doesn't get left by itself at the bottom of a page. + // I.e. we want to prevent "widows" + if ((numkids > 1) && (i == 0)) { + // If it is not near the end of the page, then just move + // on to the next child + if (kidpos < printY + printHeight*MARGIN_ADJUST) continue; + + // Otherwise, the child is near the bottom of the page, so + // break the page before this child and place this child on + // the new page. + breakPage(kidbox.getY()); + continue; + } + + // If this is the last child of a group, we don't want it to + // appear by itself at the top of a new page, so allow it to + // squeeze past the bottom margin if necessary. This helps to + // prevent "orphans" + if ((numkids > 1) && (i == numkids-1)) { + // If it fits normally, just move on to the next one + if (kidpos < printY + printHeight) continue; + + // Otherwise, if it fits with extra space, then break the + // page at the end of the group + if (kidpos < printY + printHeight/MARGIN_ADJUST) { + breakPage(allocation.getY() + allocation.getHeight()); + continue; + } + } + + // If the child is not the first or last of a group, then we use + // the bottom margin strictly. If the child fits on the page, + // then move on to the next child. + if (kidpos < printY+printHeight) continue; + + // If we get here, the child doesn't fit on this page. If it has + // no children, then break the page before this child and continue. + if (kid.getViewCount() == 0) { + breakPage(kidbox.getY()); + continue; + } + + // If we get here, then the child did not fit on the page, but it + // has kids of its own, so recurse to see if any of those kids + // will fit on the page. + paginate(kid, kidbox); + } + } + + // For a document of n pages, this list stores the lengths of pages + // 0 through n-2. The last page is assumed to have a full length + ArrayList pageLengths = new ArrayList(); + + // For a document of n pages, this list stores the starting offset of + // pages 1 through n-1. The offset of page 0 is always 0 + ArrayList pageOffsets = new ArrayList(); + + /** + * Break a page at the specified Y coordinate. Store the necessary + * information into the pageLengths and pageOffsets lists + **/ + void breakPage(double y) { + double pageLength = y-pageStart-printY; + pageStart = y-printY; + pageLengths.add(new Double(pageLength)); + pageOffsets.add(new Double(pageStart)); + } + + /** Return the number of pages. This is a Pageable method. */ + public int getNumberOfPages() { return numPages; } + + /** + * Return the PageFormat object for the specified page. This is a + * Pageable method. This implementation uses the computed length of the + * page in the returned PageFormat object. The PrinterJob will use this + * as a clipping region, which will prevent extraneous parts of the + * document from being drawn in the top and bottom margins. + **/ + public PageFormat getPageFormat(int pagenum) { + // On the last page, just return the user-specified page format + if (pagenum == numPages-1) return format; + + // Otherwise, look up the height of this page and return an + // appropriate PageFormat. + double pageLength = ((Double)pageLengths.get(pagenum)).doubleValue(); + PageFormat f = (PageFormat) format.clone(); + Paper p = f.getPaper(); + if (f.getOrientation() == PageFormat.PORTRAIT) + p.setImageableArea(printX*scalefactor, printY*scalefactor, + printWidth*scalefactor, pageLength*scalefactor); + else + p.setImageableArea(printY*scalefactor, printX*scalefactor, + pageLength*scalefactor, printWidth*scalefactor); + f.setPaper(p); + return f; + } + + /** + * This Pageable method returns the Printable object for the specified + * page. Since this class implements both Pageable and Printable, it just + * returns this. + **/ + public Printable getPrintable(int pagenum) { return this; } + + /** + * This is the basic Printable method that prints a specified page + **/ + public int print(Graphics g, PageFormat format, int pageIndex) { + // Return an error code on attempts to print past the end of the doc + if (pageIndex >= numPages) return NO_SUCH_PAGE; + + // Cast the Graphics object so we can use Java2D operations + Graphics2D g2 = (Graphics2D)g; + + // Translate to accomodate the top and left margins + g2.translate(format.getImageableX(), format.getImageableY()); + + // Scale the page by the specified scaling factor + g2.scale(scalefactor, scalefactor); + + // Display a page number centered in the area of the top margin. + // Set a new clipping region so we can draw into the top margin + // But remember the original clipping region so we can restore it + if (pageIndex > 0) { + Shape originalClip = g.getClip(); + g.setClip(new Rectangle(0, (int)-printY, + (int)printWidth, (int)printY)); + // Compute the header to display, measure it, then display it + String numString = "- " + (pageIndex+1) + " -"; + // Get string and font measurements + FontRenderContext frc = g2.getFontRenderContext(); + Rectangle2D numBounds = headerFont.getStringBounds(numString, frc); + LineMetrics metrics = headerFont.getLineMetrics(numString, frc); + g.setFont(headerFont); // Set the font + g.setColor(Color.black); // Print with black ink + g.drawString(numString, // Display the string + (int)((printWidth-numBounds.getWidth())/2), + (int)(-(printY-numBounds.getHeight())/2 + + metrics.getAscent())); + g.setClip(originalClip); // Restore the clipping region + } + + // Get the staring position and length of the page within the document + double pageStart = 0.0, pageLength = printHeight; + if (pageIndex > 0) + pageStart = ((Double)pageOffsets.get(pageIndex-1)).doubleValue(); + if (pageIndex < numPages-1) + pageLength = ((Double)pageLengths.get(pageIndex)).doubleValue(); + + // Scroll so that the appropriate part of the document is lined up + // with the upper-left corner of the page + g2.translate(0.0, -pageStart); + + // Now paint the entire document. Because of the clipping region, + // only the desired portion of the document will actually be drawn on + // this sheet of paper. + root.paint(g, drawRect); + + // Finally return a success code + return PAGE_EXISTS; + } + + /** + * This inner class is a concrete implementation of View, with a + * couple of key method implementations. An instance of this class + * is used as the parent of the root View object we want to print + **/ + static class ParentView extends View { + ViewFactory viewFactory; // The ViewFactory for the hierarchy of views + Container container; // The Container for the hierarchy of views + + public ParentView(View v, ViewFactory viewFactory, Container container) + { + super(v.getElement()); + this.viewFactory = viewFactory; + this.container = container; + } + + // These methods return key pieces of information required by + // the View hierarchy. + public ViewFactory getViewFactory() { return viewFactory; } + public Container getContainer() { return container; } + + // These methods are abstract in View, so we've got to provide + // dummy implementations of them here, even though they're never used. + public void paint(Graphics g, Shape allocation) {} + public float getPreferredSpan(int axis) { return 0.0f; } + public int viewToModel(float x,float y,Shape a,Position.Bias[] bias) { + return 0; + } + public Shape modelToView(int pos, Shape a, Position.Bias b) + throws BadLocationException { + return a; + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/print/ScribblePrinter1.java b/src/main/java/com/davidflanagan/examples/print/ScribblePrinter1.java new file mode 100644 index 0000000..630dbc6 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/print/ScribblePrinter1.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.print; +import java.awt.*; +import java.awt.event.*; +import java.util.*; + +/** + * A "scribble" application that remembers the scribble and allows the user + * to print it. It displays an AWT API and uses the Java 1.1 printing API. + **/ +public class ScribblePrinter1 extends Panel { + private short last_x = 0, last_y = 0; // last click posistion + private Vector lines = new Vector(256,256); // store the scribble + private Properties printprefs = new Properties(); // store user preferences + private Frame frame; + + public ScribblePrinter1(Frame frame) { + // Remember the frame: we'll need it to create a PrintJob + this.frame = frame; + + // Register event types we're interested in for scribbling + enableEvents(AWTEvent.MOUSE_EVENT_MASK | + AWTEvent.MOUSE_MOTION_EVENT_MASK); + + // Add a print button to he layout, and respond to it by printing + Button b = new Button("Print"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { printScribble(); } + }); + this.setLayout(new FlowLayout(FlowLayout.RIGHT, 5, 5)); + this.add(b); + } + + /** Redraw (or print) the scribble based on stored lines */ + public void paint(Graphics g) { + for(int i = 0; i < lines.size(); i++) { + Line l = (Line)lines.elementAt(i); + g.drawLine(l.x1, l.y1, l.x2, l.y2); + } + } + + /** Print out the scribble */ + public void printScribble() { + // Obtain a PrintJob + Toolkit toolkit = this.getToolkit(); + PrintJob job = toolkit.getPrintJob(frame, "ScribblePrinter1", printprefs); + + // If the user clicked Cancel in the print dialog, don't print + if (job == null) return; + + // Get the Graphics object we use to draw to the printer + Graphics g = job.getGraphics(); + + // Give the output a larger top and left margin. Otherwise it will + // be scrunched up in the upper-left corner of the page. + g.translate(100, 100); + + // Draw a border around the output area. + Dimension size = this.getSize(); + g.drawRect(-1, -1, size.width+2, size.height+2); + + // Set a clipping region so our scribbles don't go outside the border + // On-screen this happens automatically, but not on paper. + g.setClip(0, 0, size.width, size.height); + + // Print this component and all components it contains + // This will invoke the paint() method, and will paint the button too. + // Use print() instead of printAll() if you don't the button to show. + this.printAll(g); + + // Finish up. + g.dispose(); // End the current page + job.end(); // End the print job + } + + /** Called when the user clicks to begin a scribble */ + public void processMouseEvent(MouseEvent e) { + if (e.getID() == MouseEvent.MOUSE_PRESSED) { + last_x = (short)e.getX(); // remember click position + last_y = (short)e.getY(); + } + else super.processMouseEvent(e); + } + + /** Called when the the user drags the mouse: does the scribbling */ + public void processMouseMotionEvent(MouseEvent e) { + if (e.getID() == MouseEvent.MOUSE_DRAGGED) { + Graphics g = getGraphics(); + g.drawLine(last_x, last_y, e.getX(), e.getY()); // draw the line + lines.addElement(new Line(last_x, last_y, // and save it + (short) e.getX(), (short)e.getY())); + last_x = (short) e.getX(); + last_y = (short) e.getY(); + } + else super.processMouseMotionEvent(e); + } + + /** The main method. Create a ScribblePrinter1 object and away we go! */ + public static void main(String[] args) { + Frame frame = new Frame("ScribblePrinter1"); + ScribblePrinter1 s = new ScribblePrinter1(frame); + frame.add(s, BorderLayout.CENTER); + frame.setSize(400, 400); + frame.show(); + } + + /** + * This inner class stores the coordinates of one line of the scribble. + **/ + class Line { + public short x1, y1, x2, y2; + public Line(short x1, short y1, short x2, short y2) { + this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/print/ScribblePrinter2.java b/src/main/java/com/davidflanagan/examples/print/ScribblePrinter2.java new file mode 100644 index 0000000..f03fd82 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/print/ScribblePrinter2.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.print; +import java.awt.*; +import java.awt.event.*; +import java.awt.print.*; +import java.awt.geom.*; +import javax.swing.*; +import java.util.*; + +/** + * A "scribble" application that remembers the scribble and allows the user + * to print it. It displays a Swing API and uses the Java 1.2 printing API. + * It also uses Java2D features to draw and represent the scribble. + **/ +public class ScribblePrinter2 extends JComponent implements Printable { + Stroke linestyle = new BasicStroke(3.0f); // Draw with wide lines + GeneralPath scribble = new GeneralPath(); // Holds the scribble + + public ScribblePrinter2() { + // Register event types we're interested in for scribbling + enableEvents(AWTEvent.MOUSE_EVENT_MASK | + AWTEvent.MOUSE_MOTION_EVENT_MASK); + + // Add a print button to he layout, and respond to it by printing + JButton b = new JButton("Print"); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { printScribble(); } + }); + this.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5)); + this.add(b); + } + + /** Redraw (or print) the scribble based on stored lines */ + public void paintComponent(Graphics g) { + super.paintComponent(g); // Allow the superclass to draw itself + Graphics2D g2 = (Graphics2D) g; + g2.setStroke(linestyle); // Specify wide lines + g2.draw(scribble); // Draw the scribble + } + + /** + * Print out the scribble. This is the method invoked by the Print button; + * it is not part of the Printable interface + **/ + public void printScribble() { + // Obtain a java.awt.print.PrinterJob (not java.awt.PrintJob) + PrinterJob job = PrinterJob.getPrinterJob(); + + // Tell the PrinterJob to print us (since we implement Printable) + // using the default page layout + job.setPrintable(this, job.defaultPage()); + + // Display the print dialog that allows the user to set options. + // The method returns false if the user cancelled the print request + if (job.printDialog()) { + // If not cancelled, start printing! This will call the print() + // method defined by the Printable interface. + try { job.print(); } + catch (PrinterException e) { System.err.println(e); } + } + } + + /** + * This is the method defined by the Printable interface. It prints the + * scribble to the specified Graphics object, respecting the paper size + * and margins specified by the PageFormat. If the specified page number + * is not page 0, it returns a code saying that printing is complete. The + * method must be prepared to be called multiple times per printing request + **/ + public int print(Graphics g, PageFormat format, int pagenum) { + // We are only one page long; reject any other page numbers + if (pagenum > 0) return Printable.NO_SUCH_PAGE; + + // The Java 1.2 printing API passes us a Graphics object, but we + // can always cast it to a Graphics2D object + Graphics2D g2 = (Graphics2D) g; + + // Translate to accomodate the requested top and left margins. + g2.translate(format.getImageableX(), format.getImageableY()); + + // Figure out how big the drawing is, and how big the page + // (excluding margins) is + Dimension size = this.getSize(); // Scribble size + double pageWidth = format.getImageableWidth(); // Page width + double pageHeight = format.getImageableHeight(); // Page height + + // If the scribble is too wide or tall for the page, scale it down + if (size.width > pageWidth) { + double factor = pageWidth/size.width; // How much to scale + g2.scale(factor, factor); // Adjust coordinate system + pageWidth /= factor; // Adjust page size up + pageHeight /= factor; + } + if (size.height > pageHeight) { // Do the same thing for height + double factor = pageHeight/size.height; + g2.scale(factor, factor); + pageWidth /= factor; + pageHeight /= factor; + } + + // Now we know the scribble will fit on the page. Center it by + // translating as necessary. + g2.translate((pageWidth-size.width)/2,(pageHeight-size.height)/2); + + // Draw a line around the outside of the drawing area + g2.drawRect(-1, -1, size.width+2, size.height+2); + + // Set a clipping region so the scribbles don't go out of bounds + g2.setClip(0, 0, size.width, size.height); + + // Finally, print the component by calling the paintComponent() method. + // Or, call paint() to paint the component, its background, border, and + // children, including the Print JButton + this.paintComponent(g); + + // Tell the PrinterJob that the page number was valid + return Printable.PAGE_EXISTS; + } + + /** Called when the user clicks to begin a scribble */ + public void processMouseEvent(MouseEvent e) { + if (e.getID() == MouseEvent.MOUSE_PRESSED) { + scribble.moveTo(e.getX(), e.getY()); // Start a new line + } + else super.processMouseEvent(e); + } + + /** Called when the the user drags the mouse: does the scribbling */ + public void processMouseMotionEvent(MouseEvent e) { + if (e.getID() == MouseEvent.MOUSE_DRAGGED) { + scribble.lineTo(e.getX(), e.getY()); // Add a line to the scribble + repaint(); // Redraw the whole scribble. Clean but a little slow + } + else super.processMouseMotionEvent(e); + } + + /** The main method. Create a ScribblePrinter2 object and away we go! */ + public static void main(String[] args) { + JFrame frame = new JFrame("ScribblePrinter2"); + ScribblePrinter2 s = new ScribblePrinter2(); + frame.getContentPane().add(s, BorderLayout.CENTER); + frame.setSize(400, 400); + frame.setVisible(true); + } +} diff --git a/src/main/java/com/davidflanagan/examples/reflect/Command.java b/src/main/java/com/davidflanagan/examples/reflect/Command.java new file mode 100644 index 0000000..5349878 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/reflect/Command.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.reflect; +import java.awt.event.*; +import java.beans.*; +import java.lang.reflect.*; +import java.io.*; +import java.util.*; + +/** + * This class represents a Method, the list of arguments to be passed + * to that method, and the object on which the method is to be invoked. + * The invoke() method invokes the method. The actionPerformed() method + * does the same thing, allowing this class to implement ActionListener + * and be used to respond to ActionEvents generated in a GUI or elsewhere. + * The static parse() method parses a string representation of a method + * and its arguments. + **/ +public class Command implements ActionListener { + Method m; // The method to be invoked + Object target; // The object to invoke it on + Object[] args; // The arguments to pass to the method + + // An empty array; used for methods with no arguments at all. + static final Object[] nullargs = new Object[] {}; + + /** This constructor creates a Command object for a no-arg method */ + public Command(Object target, Method m) { this(target, m, nullargs); } + + /** + * This constructor creates a Command object for a method that takes the + * specified array of arguments. Note that the parse() method provides + * another way to create a Command object + **/ + public Command(Object target, Method m, Object[] args) { + this.target = target; + this.m = m; + this.args = args; + } + + /** + * Invoke the Command by calling the method on its target, and passing + * the arguments. See also actionPerformed() which does not throw the + * checked exceptions that this method does. + **/ + public void invoke() + throws IllegalAccessException, InvocationTargetException + { + m.invoke(target, args); // Use reflection to invoke the method + } + + /** + * This method implements the ActionListener interface. It is like + * invoke() except that it catches the exceptions thrown by that method + * and rethrows them as an unchecked RuntimeException + **/ + public void actionPerformed(ActionEvent e) { + try { + invoke(); // Call the invoke method + } + catch (InvocationTargetException ex) { // but handle the exceptions + throw new RuntimeException("Command: " + + ex.getTargetException().toString()); + } + catch (IllegalAccessException ex) { + throw new RuntimeException("Command: " + ex.toString()); + } + } + + /** + * This static method creates a Command using the specified target object, + * and the specified string. The string should contain method name + * followed by an optional parenthesized comma-separated argument list and + * a semicolon. The arguments may be boolean, integer or double literals, + * or double-quoted strings. The parser is lenient about missing commas, + * semicolons and quotes, but throws an IOException if it cannot parse the + * string. + **/ + public static Command parse(Object target, String text) throws IOException + { + String methodname; // The name of the method + ArrayList args = new ArrayList(); // Hold arguments as we parse them. + ArrayList types = new ArrayList(); // Hold argument types. + + // Convert the string into a character stream, and use the + // StreamTokenizer class to convert it into a stream of tokens + StreamTokenizer t = new StreamTokenizer(new StringReader(text)); + + // The first token must be the method name + int c = t.nextToken(); // read a token + if (c != t.TT_WORD) // check the token type + throw new IOException("Missing method name for command"); + methodname = t.sval; // Remember the method name + + // Now we either need a semicolon or a open paren + c = t.nextToken(); + if (c == '(') { // If we see an open paren, then parse an arg list + for(;;) { // Loop 'till end of arglist + c = t.nextToken(); // Read next token + + if (c == ')') { // See if we're done parsing arguments. + c = t.nextToken(); // If so, parse an optional semicolon + if (c != ';') t.pushBack(); + break; // Now stop the loop. + } + + // Otherwise, the token is an argument; figure out its type + if (c == t.TT_WORD) { + // If the token is an identifier, parse boolean literals, + // and treat any other tokens as unquoted string literals. + if (t.sval.equals("true")) { // Boolean literal + args.add(Boolean.TRUE); + types.add(boolean.class); + } + else if (t.sval.equals("false")) { // Boolean literal + args.add(Boolean.FALSE); + types.add(boolean.class); + } + else { // Assume its a string + args.add(t.sval); + types.add(String.class); + } + } + else if (c == '"') { // If the token is a quoted string + args.add(t.sval); + types.add(String.class); + } + else if (c == t.TT_NUMBER) { // If the token is a number + int i = (int) t.nval; + if (i == t.nval) { // Check if its an integer + // Note: this code treats a token like "2.0" as an int! + args.add(new Integer(i)); + types.add(int.class); + } + else { // Otherwise, its a double + args.add(new Double(t.nval)); + types.add(double.class); + } + } + else { // Any other token is an error + throw new IOException("Unexpected token " + t.sval + + " in argument list of " + + methodname + "()."); + } + + // Next should be a comma, but we don't complain if its not + c = t.nextToken(); + if (c != ',') t.pushBack(); + } + } + else if (c != ';') { // if a method name is not followed by a paren + t.pushBack(); // then allow a semi-colon but don't require it. + } + + // We've parsed the argument list. + // Next, convert the lists of argument values and types to arrays + Object[] argValues = args.toArray(); + Class[] argtypes = (Class[])types.toArray(new Class[argValues.length]); + + // At this point, we've got a method name, and arrays of argument + // values and types. Use reflection on the class of the target object + // to find a method with the given name and argument types. Throw + // an exception if we can't find the named method. + Method method; + try { method = target.getClass().getMethod(methodname, argtypes); } + catch (Exception e) { + throw new IOException("No such method found, or wrong argument " + + "types: " + methodname); + } + + // Finally, create and return a Command object, using the target object + // passed to this method, the Method object we obtained above, and + // the array of argument values we parsed from the string. + return new Command(target, method, argValues); + } + + /** + * This simple program demonstrates how a Command object can be parsed from + * a string and used as an ActionListener object in a Swing application. + **/ + static class Test { + public static void main(String[] args) throws IOException { + javax.swing.JFrame f = new javax.swing.JFrame("Command Test"); + javax.swing.JButton b1 = new javax.swing.JButton("Tick"); + javax.swing.JButton b2 = new javax.swing.JButton("Tock"); + javax.swing.JLabel label = new javax.swing.JLabel("Hello world"); + java.awt.Container pane = f.getContentPane(); + + pane.add(b1, java.awt.BorderLayout.WEST); + pane.add(b2, java.awt.BorderLayout.EAST); + pane.add(label, java.awt.BorderLayout.NORTH); + + b1.addActionListener(Command.parse(label, "setText(\"tick\");")); + b2.addActionListener(Command.parse(label, "setText(\"tock\");")); + + f.pack(); + f.show(); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/reflect/ShowClass.java b/src/main/java/com/davidflanagan/examples/reflect/ShowClass.java new file mode 100644 index 0000000..1e26cac --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/reflect/ShowClass.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.reflect; +import java.lang.reflect.*; + +/** A program that displays a class synopsis for the named class */ +public class ShowClass { + /** The main method. Print info about the named class */ + public static void main(String[] args) throws ClassNotFoundException { + Class c = Class.forName(args[0]); + print_class(c); + } + + /** + * Display the modifiers, name, superclass and interfaces of a class + * or interface. Then go and list all constructors, fields, and methods. + **/ + public static void print_class(Class c) + { + // Print modifiers, type (class or interface), name and superclass. + if (c.isInterface()) { + // The modifiers will include the "interface" keyword here... + System.out.print(Modifier.toString(c.getModifiers()) + " " + + typename(c)); + } + else if (c.getSuperclass() != null) { + System.out.print(Modifier.toString(c.getModifiers()) + " class " + + typename(c) + + " extends " + typename(c.getSuperclass())); + } + else { + System.out.print(Modifier.toString(c.getModifiers()) + " class " + + typename(c)); + } + + // Print interfaces or super-interfaces of the class or interface. + Class[] interfaces = c.getInterfaces(); + if ((interfaces != null)&& (interfaces.length > 0)) { + if (c.isInterface()) System.out.print(" extends "); + else System.out.print(" implements "); + for(int i = 0; i < interfaces.length; i++) { + if (i > 0) System.out.print(", "); + System.out.print(typename(interfaces[i])); + } + } + + System.out.println(" {"); // Begin class member listing. + + // Now look up and display the members of the class. + System.out.println(" // Constructors"); + Constructor[] constructors = c.getDeclaredConstructors(); + for(int i = 0; i < constructors.length; i++) // Display constructors. + print_method_or_constructor(constructors[i]); + + System.out.println(" // Fields"); + Field[] fields = c.getDeclaredFields(); // Look up fields. + for(int i = 0; i < fields.length; i++) // Display them. + print_field(fields[i]); + + System.out.println(" // Methods"); + Method[] methods = c.getDeclaredMethods(); // Look up methods. + for(int i = 0; i < methods.length; i++) // Display them. + print_method_or_constructor(methods[i]); + + System.out.println("}"); // End class member listing. + } + + /** Return the name of an interface or primitive type, handling arrays. */ + public static String typename(Class t) { + String brackets = ""; + while(t.isArray()) { + brackets += "[]"; + t = t.getComponentType(); + } + String name = t.getName(); + int pos = name.lastIndexOf('.'); + if (pos != -1) name = name.substring(pos+1); + return name + brackets; + } + + /** Return a string version of modifiers, handling spaces nicely. */ + public static String modifiers(int m) { + if (m == 0) return ""; + else return Modifier.toString(m) + " "; + } + + /** Print the modifiers, type, and name of a field */ + public static void print_field(Field f) { + System.out.println(" " + modifiers(f.getModifiers()) + + typename(f.getType()) + " " + f.getName() + ";"); + } + + /** + * Print the modifiers, return type, name, parameter types and exception + * type of a method or constructor. Note the use of the Member interface + * to allow this method to work with both Method and Constructor objects + **/ + public static void print_method_or_constructor(Member member) { + Class returntype=null, parameters[], exceptions[]; + if (member instanceof Method) { + Method m = (Method) member; + returntype = m.getReturnType(); + parameters = m.getParameterTypes(); + exceptions = m.getExceptionTypes(); + System.out.print(" " + modifiers(member.getModifiers()) + + typename(returntype) + " " + member.getName() + + "("); + } else { + Constructor c = (Constructor) member; + parameters = c.getParameterTypes(); + exceptions = c.getExceptionTypes(); + System.out.print(" " + modifiers(member.getModifiers()) + + typename(c.getDeclaringClass()) + "("); + } + + for(int i = 0; i < parameters.length; i++) { + if (i > 0) System.out.print(", "); + System.out.print(typename(parameters[i])); + } + System.out.print(")"); + if (exceptions.length > 0) System.out.print(" throws "); + for(int i = 0; i < exceptions.length; i++) { + if (i > 0) System.out.print(", "); + System.out.print(typename(exceptions[i])); + } + System.out.println(";"); + } +} diff --git a/src/main/java/com/davidflanagan/examples/rmi/Bank.java b/src/main/java/com/davidflanagan/examples/rmi/Bank.java new file mode 100644 index 0000000..23d92bd --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/rmi/Bank.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.rmi; +import java.rmi.*; +import java.util.List; + +/** + * This class is a placeholder that simply contains other classes and + * for interfaces remote banking. + **/ +public class Bank { + /** + * This is the interface that defines the exported methods of the + * bank server. + **/ + public interface RemoteBank extends Remote { + /** Open a new account, with the specified name and password */ + public void openAccount(String name, String password) + throws RemoteException, BankingException; + + /** Close the named account */ + public FunnyMoney closeAccount(String name, String password) + throws RemoteException, BankingException; + + /** Deposit money into the named account */ + public void deposit(String name, String password, FunnyMoney money) + throws RemoteException, BankingException; + + /** Withdraw the specified amount of money from the named account */ + public FunnyMoney withdraw(String name, String password, int amount) + throws RemoteException, BankingException; + + /** Return the amount of money in the named account */ + public int getBalance(String name, String password) + throws RemoteException, BankingException; + + /** + * Return a List of Strings that list the transaction history + * of the named account + **/ + public List getTransactionHistory(String name, String password) + throws RemoteException, BankingException; + } + + /** + * This simple class represents a monetary amount. This implementation + * is really nothing more than a wrapper around an integer. It is a useful + * to demonstrate that RMI can accept arbitrary non-String objects as + * arguments and return them as values, as long as they are Serializable. + * A more complete implementation of this FunnyMoney class might bear + * a serial number, a digital signature, and other security features to + * ensure that it is unique and non-forgeable. + **/ + public static class FunnyMoney implements java.io.Serializable { + public int amount; + public FunnyMoney(int amount) { this.amount = amount; } + } + + /** + * This is a type of exception used to represent exceptional conditions + * related to banking, such as "Insufficient Funds" and "Invalid Password" + **/ + public static class BankingException extends Exception { + public BankingException(String msg) { super(msg); } + } + + /** + * This class is a simple stand-alone client program that interacts + * with a RemoteBank server. It invokes different RemoteBank methods + * depending on its command-line arguments, and demonstrates just how + * simple it is to interact with a server using RMI. + **/ + public static class Client { + public static void main(String[] args) { + try { + // Figure out what RemoteBank to connect to by reading a system + // property (specified on the command line with a -D option to + // java) or, if it is not defined, use a default URL. Note + // that by default this client tries to connect to a server on + // the local machine + String url = System.getProperty("bank", "rmi:///FirstRemote"); + + // Now look up that RemoteBank server using the Naming object, + // which contacts the rmiregistry server. Given the url, this + // call returns a RemoteBank object whose methods may be + // invoked remotely + RemoteBank bank = (RemoteBank) Naming.lookup(url); + + // Convert the user's command to lower case + String cmd = args[0].toLowerCase(); + + // Now, go test the command against a bunch of possible options + if (cmd.equals("open")) { // Open an account + bank.openAccount(args[1], args[2]); + System.out.println("Account opened."); + } + else if (cmd.equals("close")) { // Close an account + FunnyMoney money = bank.closeAccount(args[1], args[2]); + // Note: our currency is denominated in wooden nickels + System.out.println(money.amount + + " wooden nickels returned to you."); + System.out.println("Thanks for banking with us."); + } + else if (cmd.equals("deposit")) { // Deposit money + FunnyMoney money=new FunnyMoney(Integer.parseInt(args[3])); + bank.deposit(args[1], args[2], money); + System.out.println("Deposited " + money.amount + + " wooden nickels."); + } + else if (cmd.equals("withdraw")) { // Withdraw money + FunnyMoney money = bank.withdraw(args[1], args[2], + Integer.parseInt(args[3])); + System.out.println("Withdrew " + money.amount + + " wooden nickels."); + } + else if (cmd.equals("balance")) { // Check account balance + int amt = bank.getBalance(args[1], args[2]); + System.out.println("You have " + amt + + " wooden nickels in the bank."); + } + else if (cmd.equals("history")) { // Get transaction history + List transactions = + bank.getTransactionHistory(args[1], args[2]); + for(int i = 0; i < transactions.size(); i++) + System.out.println(transactions.get(i)); + } + else System.out.println("Unknown command"); + } + // Catch and display RMI exceptions + catch (RemoteException e) { System.err.println(e); } + // Catch and display Banking related exceptions + catch (BankingException e) { System.err.println(e.getMessage()); } + // Other exceptions are probably user syntax errors, so show usage. + catch (Exception e) { + System.err.println(e); + System.err.println("Usage: java [-Dbank=] Bank$Client " + + " []"); + System.err.println("where cmd is: open, close, deposit, " + + "withdraw, balance, history"); + } + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/rmi/Mud.java b/src/main/java/com/davidflanagan/examples/rmi/Mud.java new file mode 100644 index 0000000..774d846 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/rmi/Mud.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.rmi; +import java.rmi.*; +import java.util.Vector; +import java.io.IOException; + +/** + * This class defines three nested Remote interfaces for use by our MUD game. + * It also defines a bunch of exception subclasses, and a constant string + * prefix used to create unique names when registering MUD servers + **/ +public class Mud { + /** + * This interface defines the exported methods of the MUD server object + **/ + public interface RemoteMudServer extends Remote { + /** Return the name of this MUD */ + public String getMudName() throws RemoteException; + + /** Return the main entrance place for this MUD */ + public RemoteMudPlace getEntrance() throws RemoteException; + + /** Look up and return some other named place in this MUD */ + public RemoteMudPlace getNamedPlace(String name) + throws RemoteException, NoSuchPlace; + + /** + * Dump the state of the server to a file so that it can be restored + * later All places, and their exits and things are dumped, but the + * "people" in them are not. + **/ + public void dump(String password, String filename) + throws RemoteException, BadPassword, IOException; + } + + /** + * This interface defines the methods exported by a "person" object that + * is in the MUD. + **/ + public interface RemoteMudPerson extends Remote { + /** Return a full description of the person */ + public String getDescription() throws RemoteException; + + /** Deliver a message to the person */ + public void tell(String message) throws RemoteException; + } + + /** + * This is the most important remote interface for the MUD. It defines the + * methods exported by the "places" or "rooms" within a MUD. Each place + * has a name and a description, and also maintains a list of "people" in + * the place, things in the place, and exits from the place. There are + * methods to get a list of names for these people, things, and exits. + * There are methods to get the RemoteMudPerson object for a named person, + * to get a description of a named thing, and to go through a named exit. + * There are methods for interacting with other people in the MUD. There + * are methods for building the MUD by creating and destroying things, + * adding new places (and new exits to those places), for linking a place + * through a new exit to some other place (possibly on another MUD server), + * and for closing down an existing exit. + **/ + public interface RemoteMudPlace extends Remote { + /** Look up the name of this place */ + public String getPlaceName() throws RemoteException; + + /** Get a description of this place */ + public String getDescription() throws RemoteException; + + /** Find out the names of all people here */ + public Vector getNames() throws RemoteException; + + /** Get the names of all things here */ + public Vector getThings() throws RemoteException; + + /** Get the names of all ways out of here */ + public Vector getExits() throws RemoteException; + + /** Get the RemoteMudPerson object for the named person. */ + public RemoteMudPerson getPerson(String name) + throws RemoteException, NoSuchPerson; + + /** Get more details about a named thing */ + public String examineThing(String name) + throws RemoteException,NoSuchThing; + + /** Use the named exit */ + public RemoteMudPlace go(RemoteMudPerson who, String direction) + throws RemoteException,NotThere,AlreadyThere,NoSuchExit,LinkFailed; + + /** Send a message of the form "David: hi everyone" */ + public void speak(RemoteMudPerson speaker, String msg) + throws RemoteException, NotThere; + + /** Send a message of the form "David laughs loudly" */ + public void act(RemoteMudPerson speaker, String msg) + throws RemoteException, NotThere; + + /** Add a new thing in this place */ + public void createThing(RemoteMudPerson who, String name, + String description) + throws RemoteException, NotThere, AlreadyThere; + + /** Remove a thing from this place */ + public void destroyThing(RemoteMudPerson who, String thing) + throws RemoteException, NotThere, NoSuchThing; + + /** + * Create a new place, bi-directionally linked to this one by an exit + **/ + public void createPlace(RemoteMudPerson creator, + String exit, String entrance, + String name, String description) + throws RemoteException,NotThere, + ExitAlreadyExists,PlaceAlreadyExists; + + /** + * Link this place (unidirectionally) to some existing place. The + * destination place may even be on another server. + **/ + public void linkTo(RemoteMudPerson who, String exit, + String hostname, String mudname, String placename) + throws RemoteException, NotThere, ExitAlreadyExists, NoSuchPlace; + + /** Remove an existing exit */ + public void close(RemoteMudPerson who, String exit) + throws RemoteException, NotThere, NoSuchExit; + + /** + * Remove this person from this place, leaving them nowhere. + * Send the specified message to everyone left in the place. + **/ + public void exit(RemoteMudPerson who, String message) + throws RemoteException, NotThere; + + /** + * Put a person in a place, assigning their name, and sending the + * specified message to everyone else in the place. The client should + * not make this method available to the user. They should use go() + * instead. + **/ + public void enter(RemoteMudPerson who, String name, String message) + throws RemoteException, AlreadyThere; + + /** + * Return the server object of the MUD that "contains" this place + * This method should not be directly visible to the player + **/ + public RemoteMudServer getServer() throws RemoteException; + } + + /** + * This is a generic exception class that serves as the superclass + * for a bunch of more specific exception types + **/ + public static class MudException extends Exception {} + + /** + * These specific exception classes are thrown in various contexts. + * The exception class name contains all the information about the + * exception; no detail messages are provided by these classes. + **/ + public static class NotThere extends MudException {} + public static class AlreadyThere extends MudException {} + public static class NoSuchThing extends MudException {} + public static class NoSuchPerson extends MudException {} + public static class NoSuchExit extends MudException {} + public static class NoSuchPlace extends MudException {} + public static class ExitAlreadyExists extends MudException {} + public static class PlaceAlreadyExists extends MudException {} + public static class LinkFailed extends MudException {} + public static class BadPassword extends MudException {} + + /** + * This constant is used as a prefix to the MUD name when the server + * registers the mud with the RMI Registry, and when the client looks + * up the MUD in the registry. Using this prefix helps prevent the + * possibility of name collisions. + **/ + static final String mudPrefix = "com.davidflanagan.examples.rmi.Mud."; +} diff --git a/src/main/java/com/davidflanagan/examples/rmi/MudClient.java b/src/main/java/com/davidflanagan/examples/rmi/MudClient.java new file mode 100644 index 0000000..bded0c5 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/rmi/MudClient.java @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.rmi; +import java.rmi.*; +import java.rmi.server.*; +import java.rmi.registry.*; +import java.io.*; +import java.util.*; +import com.davidflanagan.examples.rmi.Mud.*; + +/** + * This class is a client program for the MUD. The main() method sets up + * a connection to a RemoteMudServer, gets the initial RemoteMudPlace object, + * and creates a MudPerson object to represent the user in the MUD. Then it + * calls runMud() to put the person in the place, begins processing + * user commands. The getLine() and getMultiLine() methods are convenience + * methods used throughout to get input from the user. + **/ +public class MudClient { + /** + * The main program. It expects two or three arguments: + * 0) the name of the host on which the mud server is running + * 1) the name of the MUD on that host + * 2) the name of a place within that MUD to start at (optional). + * + * It uses the Naming.lookup() method to obtain a RemoteMudServer object + * for the named MUD on the specified host. Then it uses the getEntrance() + * or getNamedPlace() method of RemoteMudServer to obtain the starting + * RemoteMudPlace object. It prompts the user for a their name and + * description, and creates a MudPerson object. Finally, it passes + * the person and the place to runMud() to begin interaction with the MUD. + **/ + public static void main(String[] args) { + try { + String hostname = args[0]; // Each MUD is uniquely identified by a + String mudname = args[1]; // host and a MUD name. + String placename = null; // Each place in a MUD has a unique name + if (args.length > 2) placename = args[2]; + + // Look up the RemoteMudServer object for the named MUD using + // the default registry on the specified host. Note the use of + // the Mud.mudPrefix constant to help prevent naming conflicts + // in the registry. + RemoteMudServer server = + (RemoteMudServer)Naming.lookup("rmi://" + hostname + "/" + + Mud.mudPrefix + mudname); + + // If the user did not specify a place in the mud, use + // getEntrance() to get the initial place. Otherwise, call + // getNamedPlace() to find the initial place. + RemoteMudPlace location = null; + if (placename == null) location = server.getEntrance(); + else location = (RemoteMudPlace) server.getNamedPlace(placename); + + // Greet the user and ask for their name and description. + // This relies on getLine() and getMultiLine() defined below. + System.out.println("Welcome to " + mudname); + String name = getLine("Enter your name: "); + String description = getMultiLine("Please describe what " + + "people see when they look at you:"); + + // Define an output stream that the MudPerson object will use to + // display messages sent to it to the user. We'll use the console. + PrintWriter myout = new PrintWriter(System.out); + + // Create a MudPerson object to represent the user in the MUD. + // Use the specified name and description, and the output stream. + MudPerson me = new MudPerson(name, description, myout); + + // Lower this thread's priority one notch so that broadcast + // messages can appear even when we're blocking for I/O. This is + // necessary on the Linux platform, but may not be necessary on all + // platforms. + int pri = Thread.currentThread().getPriority(); + Thread.currentThread().setPriority(pri-1); + + // Finally, put the MudPerson into the RemoteMudPlace, and start + // prompting the user for commands. + runMud(location, me); + } + // If anything goes wrong, print a message and exit. + catch (Exception e) { + System.out.println(e); + System.out.println("Usage: java MudClient []"); + System.exit(1); + } + } + + /** + * This method is the main loop of the MudClient. It places the person + * into the place (using the enter() method of RemoteMudPlace). Then it + * calls the look() method to describe the place to the user, and enters a + * command loop to prompt the user for a command and process the command + **/ + public static void runMud(RemoteMudPlace entrance, MudPerson me) + throws RemoteException + { + RemoteMudPlace location = entrance; // The current place + String myname = me.getName(); // The person's name + String placename = null; // The name of the current place + String mudname = null; // The name of the mud of that place + + try { + // Enter the MUD + location.enter(me, myname, myname + " has entered the MUD."); + // Figure out where we are (for the prompt) + mudname = location.getServer().getMudName(); + placename = location.getPlaceName(); + // Describe the place to the user + look(location); + } + catch (Exception e) { + System.out.println(e); + System.exit(1); + } + + // Now that we've entered the MUD, begin a command loop to process + // the user's commands. Note that there is a huge block of catch + // statements at the bottom of the loop to handle all the things that + // could go wrong each time through the loop. + for(;;) { // Loop until the user types "quit" + try { // Catch any exceptions that occur in the loop + // Pause just a bit before printing the prompt, to give output + // generated indirectly by the last command a chance to appear. + try { Thread.sleep(200); } catch (InterruptedException e) {} + + // Display a prompt, and get the user's input + String line = getLine(mudname + '.' + placename + "> "); + + // Break the input into a command and an argument that consists + // of the rest of the line. Convert the command to lowercase. + String cmd, arg; + int i = line.indexOf(' '); + if (i == -1) { cmd = line; arg = null; } + else { + cmd = line.substring(0, i).toLowerCase(); + arg = line.substring(i+1); + } + if (arg == null) arg = ""; + + // Now go process the command. What follows is a huge repeated + // if/else statement covering each of the commands supported by + // this client. Many of these commands simply invoke one of + // the remote methods of the current RemoteMudPlace object. + // Some have to do a bit of additional processing. + + // LOOK: Describe the place and its things, people, and exits + if (cmd.equals("look")) look(location); + // EXAMINE: Describe a named thing + else if (cmd.equals("examine")) + System.out.println(location.examineThing(arg)); + // DESCRIBE: Describe a named person + else if (cmd.equals("describe")) { + try { + RemoteMudPerson p = location.getPerson(arg); + System.out.println(p.getDescription()); + } + catch(RemoteException e) { + System.out.println(arg + " is having technical " + + "difficulties. No description " + + "is available."); + } + } + // GO: Go in a named direction + else if (cmd.equals("go")) { + location = location.go(me, arg); + mudname = location.getServer().getMudName(); + placename = location.getPlaceName(); + look(location); + } + // SAY: Say something to everyone + else if (cmd.equals("say")) location.speak(me, arg); + // DO: Do something that will be described to everyone + else if (cmd.equals("do")) location.act(me, arg); + // TALK: Say something to one named person + else if (cmd.equals("talk")) { + try { + RemoteMudPerson p = location.getPerson(arg); + String msg = getLine("What do you want to say?: "); + p.tell(myname + " says \"" + msg + "\""); + } + catch (RemoteException e) { + System.out.println(arg + " is having technical " + + "difficulties. Can't talk to them."); + } + } + // CHANGE: Change my own description + else if (cmd.equals("change")) + me.setDescription( + getMultiLine("Describe yourself for others: ")); + // CREATE: Create a new thing in this place + else if (cmd.equals("create")) { + if (arg.length() == 0) + throw new IllegalArgumentException("name expected"); + String desc = getMultiLine("Please describe the " + + arg + ": "); + location.createThing(me, arg, desc); + } + // DESTROY: Destroy a named thing + else if (cmd.equals("destroy")) location.destroyThing(me, arg); + // OPEN: Create a new place and connect this place to it + // through the exit specified in the argument. + else if (cmd.equals("open")) { + if (arg.length() == 0) + throw new IllegalArgumentException("direction expected"); + String name = getLine("What is the name of place there?: "); + String back = getLine("What is the direction from " + + "there back to here?: "); + String desc = getMultiLine("Please describe " + + name + ":"); + location.createPlace(me, arg, back, name, desc); + } + // CLOSE: Close a named exit. Note: only closes an exit + // uni-directionally, and does not destroy a place. + else if (cmd.equals("close")) { + if (arg.length() == 0) + throw new IllegalArgumentException("direction expected"); + location.close(me, arg); + } + // LINK: Create a new exit that connects to an existing place + // that may be in another MUD running on another host + else if (cmd.equals("link")) { + if (arg.length() == 0) + throw new IllegalArgumentException("direction expected"); + String host = getLine("What host are you linking to?: "); + String mud = + getLine("What is the name of the MUD on that host?: "); + String place = + getLine("What is the place name in that MUD?: "); + location.linkTo(me, arg, host, mud, place); + System.out.println("Don't forget to make a link from " + + "there back to here!"); + } + // DUMP: Save the state of this MUD into the named file, + // if the password is correct + else if (cmd.equals("dump")) { + if (arg.length() == 0) + throw new IllegalArgumentException("filename expected"); + String password = getLine("Password: "); + location.getServer().dump(password, arg); + } + // QUIT: Quit the game + else if (cmd.equals("quit")) { + try { location.exit(me, myname + " has quit."); } + catch (Exception e) {} + System.out.println("Bye."); + System.out.flush(); + System.exit(0); + } + // HELP: Print out a big help message + else if (cmd.equals("help")) System.out.println(help); + // Otherwise, this is an unrecognized command. + else System.out.println("Unknown command. Try 'help'."); + } + // Handle the many possible types of MudException + catch (MudException e) { + if (e instanceof NoSuchThing) + System.out.println("There isn't any such thing here."); + else if (e instanceof NoSuchPerson) + System.out.println("There isn't anyone by that name here."); + else if (e instanceof NoSuchExit) + System.out.println("There isn't an exit in that direction."); + else if (e instanceof NoSuchPlace) + System.out.println("There isn't any such place."); + else if (e instanceof ExitAlreadyExists) + System.out.println("There is already an exit " + + "in that direction."); + else if (e instanceof PlaceAlreadyExists) + System.out.println("There is already a place " + + "with that name."); + else if (e instanceof LinkFailed) + System.out.println("That exit is not functioning."); + else if (e instanceof BadPassword) + System.out.println("Invalid password."); + else if (e instanceof NotThere) // Shouldn't happen + System.out.println("You can't do that when " + + "you're not there."); + else if (e instanceof AlreadyThere) // Shouldn't happen + System.out.println("You can't go there; " + + "you're already there."); + } + // Handle RMI exceptions + catch (RemoteException e) { + System.out.println("The MUD is having technical difficulties."); + System.out.println("Perhaps the server has crashed:"); + System.out.println(e); + } + // Handle everything else that could go wrong. + catch (Exception e) { + System.out.println("Syntax or other error:"); + System.out.println(e); + System.out.println("Try using the 'help' command."); + } + } + } + + /** + * This convenience method is used in several places in the + * runMud() method above. It displays the name and description of + * the current place (including the name of the mud the place is in), + * and also displays the list of things, people, and exits in + * the current place. + **/ + public static void look(RemoteMudPlace p) + throws RemoteException, MudException + { + String mudname = p.getServer().getMudName(); // Mud name + String placename = p.getPlaceName(); // Place name + String description = p.getDescription(); // Place description + Vector things = p.getThings(); // List of things here + Vector names = p.getNames(); // List of people here + Vector exits = p.getExits(); // List of exits from here + + // Print it all out + System.out.println("You are in: " + placename + + " of the Mud: " + mudname); + System.out.println(description); + System.out.print("Things here: "); + for(int i = 0; i < things.size(); i++) { // Display list of things + if (i > 0) System.out.print(", "); + System.out.print(things.elementAt(i)); + } + System.out.print("\nPeople here: "); + for(int i = 0; i < names.size(); i++) { // Display list of people + if (i > 0) System.out.print(", "); + System.out.print(names.elementAt(i)); + } + System.out.print("\nExits are: "); + for(int i = 0; i < exits.size(); i++) { // Display list of exits + if (i > 0) System.out.print(", "); + System.out.print(exits.elementAt(i)); + } + System.out.println(); // Blank line + System.out.flush(); // Make it appear now! + } + + /** This static input stream reads lines from the console */ + static BufferedReader in = + new BufferedReader(new InputStreamReader(System.in)); + + /** + * A convenience method for prompting the user and getting a line of + * input. It guarantees that the line is not empty and strips off + * whitespace at the beginning and end of the line. + **/ + public static String getLine(String prompt) { + String line = null; + do { // Loop until a non-empty line is entered + try { + System.out.print(prompt); // Display prompt + System.out.flush(); // Display it right away + line = in.readLine(); // Get a line of input + if (line != null) line = line.trim(); // Strip off whitespace + } catch (Exception e) {} // Ignore any errors + } while((line == null) || (line.length() == 0)); + return line; + } + + /** + * A convenience method for getting multi-line input from the user. + * It prompts for the input, displays instructions, and guarantees that + * the input is not empty. It also allows the user to enter the name of + * a file from which text will be read. + **/ + public static String getMultiLine(String prompt) { + String text = ""; + for(;;) { // We'll break out of this loop when we get non-empty input + try { + BufferedReader br = in; // The stream to read from + System.out.println(prompt); // Display the prompt + // Display some instructions + System.out.println("You can enter multiple lines. " + + "End with a '.' on a line by itself.\n" + + "Or enter a '<<' followed by a filename"); + // Make the prompt and instructions appear now. + System.out.flush(); + // Read lines + String line; + while((line = br.readLine()) != null) { // Until EOF + if (line.equals(".")) break; // Or until a dot by itself + // Or, if a file is specified, start reading from it + // instead of from the console. + if (line.trim().startsWith("<<")) { + String filename = line.trim().substring(2).trim(); + br = new BufferedReader(new FileReader(filename)); + continue; // Don't count the << as part of the input + } + // Add the line to the collected input + else text += line + "\n"; + } + // If we got at least one line, return it. Otherwise, chastise + // the user and go back to the prompt and the instructions. + if (text.length() > 0) return text; + else System.out.println("Please enter at least one line."); + } + // If there were errors, for example an IO error reading a file, + // display the error and loop again, displaying prompt and + // instructions + catch(Exception e) { System.out.println(e); } + } + } + + /** This is the usage string that explains the available commands */ + static final String help = + "Commands are:\n" + + "look: Look around\n" + + "examine : examine the named thing in more detail\n" + + "describe : describe the named person\n" + + "go : go in the named direction (i.e. a named exit)\n" + + "say : say something to everyone\n" + + "do : tell everyone that you are doing something\n" + + "talk : talk to one person. Will prompt for message\n" + + "change: change how you are described. Will prompt for input\n" + + "create : create a new thing. Prompts for description \n" + + "destroy : destroy a thing.\n" + + "open : create an adjoining place. Prompts for input\n"+ + "close : close an exit from this place.\n" + + "link : create an exit to an existing place,\n" + + " perhaps on another server. Will prompt for input.\n" + + "dump : save server state. Prompts for password\n" + + "quit: leave the Mud\n" + + "help: display this message"; +} diff --git a/src/main/java/com/davidflanagan/examples/rmi/MudPerson.java b/src/main/java/com/davidflanagan/examples/rmi/MudPerson.java new file mode 100644 index 0000000..1e7a133 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/rmi/MudPerson.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.rmi; +import java.rmi.*; +import java.rmi.server.*; +import java.io.*; +import com.davidflanagan.examples.rmi.Mud.*; + +/** + * This is the simplest of the remote objects that we implement for the MUD. + * It maintains only a little bit of state, and has only two exported + * methods + **/ +public class MudPerson extends UnicastRemoteObject implements RemoteMudPerson { + String name; // The name of the person + String description; // The person's description + PrintWriter tellStream; // Where to send messages we receive to + + public MudPerson(String n, String d, PrintWriter out) + throws RemoteException + { + name = n; + description = d; + tellStream = out; + } + + /** Return the person's name. Not a remote method */ + public String getName() { return name; } + + /** Set the person's name. Not a remote method */ + public void setName(String n) { name = n; } + + /** Set the person's description. Not a remote method */ + public void setDescription(String d) { description = d; } + + /** Set the stream that messages to us should be written to. Not remote. */ + public void setTellStream(PrintWriter out) { tellStream = out; } + + /** A remote method that returns this person's description */ + public String getDescription() throws RemoteException { + return description; + } + + /** + * A remote method that delivers a message to the person. + * I.e. it delivers a message to the user controlling the "person" + **/ + public void tell(String message) throws RemoteException { + tellStream.println(message); + tellStream.flush(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/rmi/MudPlace.java b/src/main/java/com/davidflanagan/examples/rmi/MudPlace.java new file mode 100644 index 0000000..1244632 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/rmi/MudPlace.java @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.rmi; +import java.rmi.*; +import java.rmi.server.*; +import java.rmi.registry.*; +import java.io.*; +import java.util.*; +import com.davidflanagan.examples.rmi.Mud.*; + +/** + * This class implements the RemoteMudPlace interface and exports a + * bunch of remote methods that are at the heart of the MUD. The + * MudClient interacts primarily with these methods. See the comment + * for RemoteMudPlace for an overview. + * The MudPlace class is Serializable so that places can be saved to disk + * along with the MudServer that contains them. Note, however that the + * names and people fields are marked transient, so they are not serialized + * along with the place (because it wouldn't make sense to try to save + * RemoteMudPerson objects, even if they could be serialized). + **/ +public class MudPlace extends UnicastRemoteObject + implements RemoteMudPlace, Serializable +{ + String placename, description; // information about the place + Vector exits = new Vector(); // names of exits from this place + Vector destinations = new Vector(); // where the exits go to + Vector things = new Vector(); // names of things in this place + Vector descriptions = new Vector(); // descriptions of those things + transient Vector names = new Vector(); // names of people in this place + transient Vector people = new Vector(); // the RemoteMudPerson objects + MudServer server; // the server for this place + + /** A no-arg constructor for de-serialization only. Do not call it */ + public MudPlace() throws RemoteException { super(); } + + /** + * This constructor creates a place, and calls a server method + * to register the object so that it will be accessible by name + **/ + public MudPlace(MudServer server, String placename, String description) + throws RemoteException, PlaceAlreadyExists + { + this.server = server; + this.placename = placename; + this.description = description; + server.setPlaceName(this, placename); // Register the place + } + + /** This remote method returns the name of this place */ + public String getPlaceName() throws RemoteException { return placename; } + + /** This remote method returns the description of this place */ + public String getDescription() throws RemoteException { + return description; + } + + /** This remote method returns a Vector of names of people in this place */ + public Vector getNames() throws RemoteException { return names; } + + /** This remote method returns a Vector of names of things in this place */ + public Vector getThings() throws RemoteException { return things; } + + /** This remote method returns a Vector of names of exits from this place*/ + public Vector getExits() throws RemoteException { return exits; } + + /** + * This remote method returns a RemoteMudPerson object corresponding to + * the specified name, or throws an exception if no such person is here + **/ + public RemoteMudPerson getPerson(String name) + throws RemoteException, NoSuchPerson + { + synchronized(names) { + // What about when there are 2 of the same name? + int i = names.indexOf(name); + if (i == -1) throw new NoSuchPerson(); + return (RemoteMudPerson) people.elementAt(i); + } + } + + /** + * This remote method returns a description of the named thing, or + * throws an exception if no such thing is in this place. + **/ + public String examineThing(String name) throws RemoteException, NoSuchThing + { + synchronized(things) { + int i = things.indexOf(name); + if (i == -1) throw new NoSuchThing(); + return (String) descriptions.elementAt(i); + } + } + + /** + * This remote method moves the specified RemoteMudPerson from this place + * in the named direction (i.e. through the named exit) to whatever place + * is there. It throws exceptions if the specified person isn't in this + * place to begin with, or if they are already in the place through the + * exit or if the exit doesn't exist, or if the exit links to another MUD + * server and the server is not functioning. + **/ + public RemoteMudPlace go(RemoteMudPerson who, String direction) + throws RemoteException, NotThere, AlreadyThere, NoSuchExit, LinkFailed + { + // Make sure the direction is valid, and get destination if it is + Object destination; + synchronized(exits) { + int i = exits.indexOf(direction); + if (i == -1) throw new NoSuchExit(); + destination = destinations.elementAt(i); + } + + // If destination is a string, it is a place on another server, so + // connect to that server. Otherwise, it is a place already on this + // server. Throw an exception if we can't connect to the server. + RemoteMudPlace newplace; + if (destination instanceof String) { + try { + String t = (String) destination; + int pos = t.indexOf('@'); + String url = t.substring(0, pos); + String placename = t.substring(pos+1); + RemoteMudServer s = (RemoteMudServer) Naming.lookup(url); + newplace = s.getNamedPlace(placename); + } + catch (Exception e) { throw new LinkFailed(); } + } + // If the destination is not a string, then it is a Place + else newplace = (RemoteMudPlace) destination; + + // Make sure the person is here and get their name. + // Throw an exception if they are not here + String name = verifyPresence(who); + + // Move the person out of here, and tell everyone who remains about it. + this.exit(who, name + " has gone " + direction); + + // Put the person into the new place. + // Send a message to everyone already in that new place + String fromwhere; + if (newplace instanceof MudPlace) // going to a local place + fromwhere = placename; + else + fromwhere = server.getMudName() + "." + placename; + newplace.enter(who, name, name + " has arrived from: " + fromwhere); + + // Return the new RemoteMudPlace object to the client so they + // know where they are now at. + return newplace; + } + + /** + * This remote method sends a message to everyone in the room. Used to + * say things to everyone. Requires that the speaker be in this place. + **/ + public void speak(RemoteMudPerson speaker, String msg) + throws RemoteException, NotThere + { + String name = verifyPresence(speaker); + tellEveryone(name + ":" + msg); + } + + /** + * This remote method sends a message to everyone in the room. Used to + * do things that people can see. Requires that the actor be in this place. + **/ + public void act(RemoteMudPerson actor, String msg) + throws RemoteException, NotThere + { + String name = verifyPresence(actor); + tellEveryone(name + " " + msg); + } + + /** + * This remote method creates a new thing in this room. + * It requires that the creator be in this room. + **/ + public void createThing(RemoteMudPerson creator, + String name, String description) + throws RemoteException, NotThere, AlreadyThere + { + // Make sure the creator is here + String creatorname = verifyPresence(creator); + synchronized(things) { + // Make sure there isn't already something with this name. + if (things.indexOf(name) != -1) throw new AlreadyThere(); + // Add the thing name and descriptions to the appropriate lists + things.addElement(name); + descriptions.addElement(description); + } + // Tell everyone about the new thing and its creator + tellEveryone(creatorname + " has created a " + name); + } + + /** + * Remove a thing from this room. Throws exceptions if the person + * who removes it isn't themselves in the room, or if there is no + * such thing here. + **/ + public void destroyThing(RemoteMudPerson destroyer, String thing) + throws RemoteException, NotThere, NoSuchThing + { + // Verify that the destroyer is here + String name = verifyPresence(destroyer); + synchronized(things) { + // Verify that there is a thing by that name in this room + int i = things.indexOf(thing); + if (i == -1) throw new NoSuchThing(); + // And remove its name and description from the lists + things.removeElementAt(i); + descriptions.removeElementAt(i); + } + // Let everyone know of the demise of this thing. + tellEveryone(name + " had destroyed the " + thing); + } + + /** + * Create a new place in this MUD, with the specified name an description. + * The new place is accessible from this place through + * the specified exit, and this place is accessible from the new place + * through the specified entrance. The creator must be in this place + * in order to create a exit from this place. + **/ + public void createPlace(RemoteMudPerson creator, + String exit, String entrance, String name, + String description) + throws RemoteException,NotThere,ExitAlreadyExists,PlaceAlreadyExists + { + // Verify that the creator is actually here in this place + String creatorname = verifyPresence(creator); + synchronized(exits) { // Only one client may change exits at a time + // Check that the exit doesn't already exist. + if (exits.indexOf(exit) != -1) throw new ExitAlreadyExists(); + // Create the new place, registering its name with the server + MudPlace destination = new MudPlace(server, name, description); + // Link from there back to here + destination.exits.addElement(entrance); + destination.destinations.addElement(this); + // And link from here to there + exits.addElement(exit); + destinations.addElement(destination); + } + // Let everyone know about the new exit, and the new place beyond + tellEveryone(creatorname + " has created a new place: " + exit); + } + + /** + * Create a new exit from this mud, linked to a named place in a named + * MUD on a named host (this can also be used to link to a named place in + * the current MUD, of course). Because of the possibilities of deadlock, + * this method only links from here to there; it does not create a return + * exit from there to here. That must be done with a separate call. + **/ + public void linkTo(RemoteMudPerson linker, String exit, + String hostname, String mudname, String placename) + throws RemoteException, NotThere, ExitAlreadyExists, NoSuchPlace + { + // Verify that the linker is actually here + String name = verifyPresence(linker); + + // Check that the link target actually exists. Throw NoSuchPlace if + // not. Note that NoSuchPlace may also mean "NoSuchMud" or + // "MudNotResponding". + String url = "rmi://" + hostname + '/' + Mud.mudPrefix + mudname; + try { + RemoteMudServer s = (RemoteMudServer) Naming.lookup(url); + RemoteMudPlace destination = s.getNamedPlace(placename); + } + catch (Exception e) { throw new NoSuchPlace(); } + + synchronized(exits) { + // Check that the exit doesn't already exist. + if (exits.indexOf(exit) != -1) throw new ExitAlreadyExists(); + // Add the exit, to the list of exit names + exits.addElement(exit); + // And add the destination to the list of destinations. Note that + // the destination is stored as a string rather than as a + // RemoteMudPlace. This is because if the remote server goes down + // then comes back up again, a RemoteMudPlace is not valid, but the + // string still is. + destinations.addElement(url + '@' + placename); + } + // Let everyone know about the new exit and where it leads + tellEveryone(name + " has linked " + exit + " to " + + "'" + placename + "' in MUD '" + mudname + + "' on host " + hostname); + } + + /** + * Close an exit that leads out of this place. + * It does not close the return exit from there back to here. + * Note that this method does not destroy the place that the exit leads to. + * In the current implementation, there is no way to destroy a place. + **/ + public void close(RemoteMudPerson who, String exit) + throws RemoteException, NotThere, NoSuchExit + { + // check that the person closing the exit is actually here + String name = verifyPresence(who); + synchronized(exits) { + // Check that the exit exists + int i = exits.indexOf(exit); + if (i == -1) throw new NoSuchExit(); + // Remove it and its destination from the lists + exits.removeElementAt(i); + destinations.removeElementAt(i); + } + // Let everyone know that the exit doesn't exist anymore + tellEveryone(name + " has closed exit " + exit); + } + + /** + * Remove a person from this place. If there is a message, send it to + * everyone who is left in this place. If the specified person is not here + * this method does nothing and does not throw an exception. This method + * is called by go(), and the client should call it when the user quits. + * The client should not allow the user to invoke it directly, however. + **/ + public void exit(RemoteMudPerson who, String message) + throws RemoteException + { + String name; + synchronized(names) { + int i = people.indexOf(who); + if (i == -1) return; + names.removeElementAt(i); + people.removeElementAt(i); + } + if (message != null) tellEveryone(message); + } + + /** + * This method puts a person into this place, assigning them the + * specified name, and displaying a message to anyone else who is in + * that place. This method is called by go(), and the client should + * call it to initially place a person into the MUD. Once the person + * is in the MUD, however, the client should restrict them to using go() + * and should not allow them to call this method directly. + * If there have been networking problems, a client might call this method + * to restore a person to this place, in case they've been bumped out. + * (A person will be bumped out of a place if the server tries to send + * a message to them and gets a RemoteException.) + **/ + public void enter(RemoteMudPerson who, String name, String message) + throws RemoteException, AlreadyThere + { + // Send the message to everyone who is already here. + if (message != null) tellEveryone(message); + + // Add the person to this place. + synchronized (names) { + if (people.indexOf(who) != -1) throw new AlreadyThere(); + names.addElement(name); + people.addElement(who); + } + } + + /** + * This final remote method returns the server object for the MUD in which + * this place exists. The client should not allow the user to invoke this + * method. + **/ + public RemoteMudServer getServer() throws RemoteException { + return server; + } + + /** + * Create and start a thread that sends out a message everyone in this + * place. If it gets a RemoteException talking to a person, it silently + * removes that person from this place. This is not a remote method, but + * is used internally by a number of remote methods. + **/ + protected void tellEveryone(final String message) { + // If there is no-one here, don't bother sending the message! + if (people.size() == 0) return; + // Make a copy of the people here now. The message is sent + // asynchronously and the list of people in the room may change before + // the message is sent to everyone. + final Vector recipients = (Vector) people.clone(); + // Create and start a thread to send the message, using an anonymous + // class. We do this because sending the message to everyone in this + // place might take some time, (particularly on a slow or flaky + // network) and we don't want to wait. + new Thread() { + public void run() { + // Loop through the recipients + for(int i = 0; i < recipients.size(); i++) { + RemoteMudPerson person = + (RemoteMudPerson)recipients.elementAt(i); + // Try to send the message to each one. + try { person.tell(message); } + // If it fails, assume that that person's client or + // network has failed, and silently remove them from + // this place. + catch (RemoteException e) { + try { MudPlace.this.exit(person, null); } + catch (Exception ex) {} + } + } + } + }.start(); + } + + /** + * This convenience method checks whether the specified person is here. + * If so, it returns their name. If not it throws a NotThere exception + **/ + protected String verifyPresence(RemoteMudPerson who) throws NotThere { + int i = people.indexOf(who); + if (i == -1) throw new NotThere(); + else return (String) names.elementAt(i); + } + + /** + * This method is used for custom de-serialization. Since the vectors of + * people and of their names are transient, they are not serialized with + * the rest of this place. Therefore, when the place is de-serialized, + * those vectors have to be recreated (empty). + **/ + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException { + in.defaultReadObject(); // Read most of the object as normal + names = new Vector(); // Then recreate the names vector + people = new Vector(); // and recreate the people vector + } + + /** This constant is a version number for serialization */ + static final long serialVersionUID = 5090967989223703026L; +} diff --git a/src/main/java/com/davidflanagan/examples/rmi/MudServer.java b/src/main/java/com/davidflanagan/examples/rmi/MudServer.java new file mode 100644 index 0000000..3d5f1d1 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/rmi/MudServer.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.rmi; +import java.rmi.*; +import java.rmi.server.*; +import java.rmi.registry.*; +import java.io.*; +import java.util.Hashtable; +import java.util.zip.*; +import com.davidflanagan.examples.rmi.Mud.*; + +/** + * This class implements the RemoteMudServer interface. It also defines a + * main() method so you can run it as a standalone program that will + * set up and initialize a MUD server. Note that a MudServer maintains an + * entrance point to a MUD, but it is not the MUD itself. Most of the + * interesting MUD functionality is defined by the RemoteMudPlace interface + * and implemented by the RemotePlace class. In addition to being a remote + * object, this class is also Serializable, so that the state of the MUD + * can be saved to a file and later restored. Note that the main() method + * defines two ways of starting a MUD: one is to start it from scratch with + * a single initial place, and another is to restore an existing MUD from a + * file. + **/ +public class MudServer extends UnicastRemoteObject + implements RemoteMudServer, Serializable +{ + MudPlace entrance; // The standard entrance to this MUD + String password; // The password required to dump() the state of the MUD + String mudname; // The name that this MUD is registered under + Hashtable places; // A mapping of place names to places in this MUD + + /** + * Start a MUD from scratch, with the given name and password. Create + * an initial MudPlace object as the entrance, giving it the specified + * name and description. + **/ + public MudServer(String mudname, String password, + String placename, String description) + throws RemoteException + { + this.mudname = mudname; + this.password = password; + this.places = new Hashtable(); + // Create the entrance place + try { this.entrance = new MudPlace(this, placename, description); } + catch (PlaceAlreadyExists e) {} // Should never happen + } + + /** For serialization only. Never call this constructor. */ + public MudServer() throws RemoteException {} + + /** This remote method returns the name of the MUD */ + public String getMudName() throws RemoteException { return mudname; } + + /** This remote method returns the entrance place of the MUD */ + public RemoteMudPlace getEntrance() throws RemoteException { + return entrance; + } + + /** + * This remote method returns a RemoteMudPlace object for the named place. + * In this sense, a MudServer acts as like an RMI Registry object, + * returning remote objects looked up by name. It is simpler to do it this + * way than to use an actual Registry object. If the named place does not + * exist, it throws a NoSuchPlace exception + **/ + public RemoteMudPlace getNamedPlace(String name) + throws RemoteException, NoSuchPlace + { + RemoteMudPlace p = (RemoteMudPlace) places.get(name); + if (p == null) throw new NoSuchPlace(); + return p; + } + + /** + * Define a new placename to place mapping in our hashtable. + * This is not a remote method. The MudPlace() constructor calls it + * to register the new place it is creating. + **/ + public void setPlaceName(RemoteMudPlace place, String name) + throws PlaceAlreadyExists + { + if (places.containsKey(name)) throw new PlaceAlreadyExists(); + places.put(name, place); + } + + /** + * This remote method serializes and compresses the state of the MUD + * to a named file, if the specified password matches the one specified + * when the MUD was initially created. Note that the state of a MUD + * consists of all places in the MUD, with all things and exits in those + * places. The people in the MUD are not part of the state that is saved. + **/ + public void dump(String password, String f) + throws RemoteException, BadPassword, IOException + { + if ((this.password != null)&& !this.password.equals(password)) + throw new BadPassword(); + ObjectOutputStream out = new ObjectOutputStream( + new GZIPOutputStream(new FileOutputStream(f))); + out.writeObject(this); + out.close(); + } + + /** + * This main() method defines the standalone program that starts up a MUD + * server. If invoked with a single argument, it treats that argument as + * the name of a file containing the serialized and compressed state of an + * existing MUD, and recreates it. Otherwise, it expects four command-line + * arguments: the name of the MUD, the password, the name of the entrance + * place for the MUD, and a description of that entrance place. + * Besides creating the MudServer object, this program sets an appropriate + * security manager, and uses the default rmiregistry to register the + * the MudServer under its given name. + **/ + public static void main(String[] args) { + try { + MudServer server; + if (args.length == 1) { + // Read the MUD state in from a file + FileInputStream f = new FileInputStream(args[0]); + ObjectInputStream in = + new ObjectInputStream(new GZIPInputStream(f)); + server = (MudServer) in.readObject(); + } + // Otherwise, create an initial MUD from scratch + else server = new MudServer(args[0], args[1], args[2], args[3]); + + Naming.rebind(Mud.mudPrefix + server.mudname, server); + } + // Display an error message if anything goes wrong. + catch (Exception e) { + System.out.println(e); + System.out.println("Usage: java MudServer \n" + + " or: java MudServer " + + " "); + System.exit(1); + } + } + + /** This constant is a version number for serialization */ + static final long serialVersionUID = 7453281245880199453L; +} diff --git a/src/main/java/com/davidflanagan/examples/rmi/RemoteBankServer.java b/src/main/java/com/davidflanagan/examples/rmi/RemoteBankServer.java new file mode 100644 index 0000000..e3a37cf --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/rmi/RemoteBankServer.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.rmi; +import java.rmi.*; +import java.rmi.server.*; +import java.util.*; +import Bank.*; + +/** + * This class implements the remote methods defined by the RemoteBank + * interface. It has a serious shortcoming, though: all account data is + * lost when the server goes down. + **/ +public class RemoteBankServer extends UnicastRemoteObject implements RemoteBank +{ + /** + * This nested class stores data for a single account with the bank + **/ + class Account { + String password; // account password + int balance; // account balance + List transactions = new ArrayList(); // account transaction history + Account(String password) { + this.password = password; + transactions.add("Account opened at " + new Date()); + } + } + + /** + * This hashtable stores all open accounts and maps from account name + * to Account object + **/ + Map accounts = new HashMap(); + + /** + * This constructor doesn't do anything, but because the superclass + * constructor throws an exception, the exception must be declared here + **/ + public RemoteBankServer() throws RemoteException { super(); } + + /** + * Open a bank account with the specified name and password + * This method is synchronized to make it thread safe, since it + * manipulates the accounts hashtable. + **/ + public synchronized void openAccount(String name, String password) + throws RemoteException, BankingException + { + // Check if there is already an account under that name + if (accounts.get(name) != null) + throw new BankingException("Account already exists."); + // Otherwise, it doesn't exist, so create it. + Account acct = new Account(password); + // And register it + accounts.put(name, acct); + } + + /** + * This internal method is not a remote method. Given a name and password + * it checks to see if an account with that name and password exists. If + * so, it returns the Account object. Otherwise, it throws an exception. + **/ + Account verify(String name, String password) throws BankingException { + synchronized(accounts) { + Account acct = (Account)accounts.get(name); + if (acct == null) throw new BankingException("No such account"); + if (!password.equals(acct.password)) + throw new BankingException("Invalid password"); + return acct; + } + } + + /** + * Close the named account. This method is synchronized to make it + * thread safe, since it manipulates the accounts hashtable. + **/ + public synchronized FunnyMoney closeAccount(String name, String password) + throws RemoteException, BankingException + { + Account acct; + acct = verify(name, password); + accounts.remove(name); + // Before changing the balance or transactions of any account, we first + // have to obtain a lock on that account to be thread safe. + synchronized (acct) { + int balance = acct.balance; + acct.balance = 0; + return new FunnyMoney(balance); + } + } + + /** Deposit the specified FunnyMoney to the named account */ + public void deposit(String name, String password, FunnyMoney money) + throws RemoteException, BankingException + { + Account acct = verify(name, password); + synchronized(acct) { + acct.balance += money.amount; + acct.transactions.add("Deposited " + money.amount + + " on " + new Date()); + } + } + + /** Withdraw the specified amount from the named account */ + public FunnyMoney withdraw(String name, String password, int amount) + throws RemoteException, BankingException + { + Account acct = verify(name, password); + synchronized(acct) { + if (acct.balance < amount) + throw new BankingException("Insufficient Funds"); + acct.balance -= amount; + acct.transactions.add("Withdrew " + amount + " on "+new Date()); + return new FunnyMoney(amount); + } + } + + /** Return the current balance in the named account */ + public int getBalance(String name, String password) + throws RemoteException, BankingException + { + Account acct = verify(name, password); + synchronized(acct) { return acct.balance; } + } + + /** + * Return a Vector of strings containing the transaction history + * for the named account + **/ + public List getTransactionHistory(String name, String password) + throws RemoteException, BankingException + { + Account acct = verify(name, password); + synchronized(acct) { return acct.transactions; } + } + + /** + * The main program that runs this RemoteBankServer. + * Create a RemoteBankServer object and give it a name in the registry. + * Read a system property to determine the name, but use "FirstRemote" + * as the default name. This is all that is necessary to set up the + * service. RMI takes care of the rest. + **/ + public static void main(String[] args) { + try { + // Create a bank server object + RemoteBankServer bank = new RemoteBankServer(); + // Figure out what to name it + String name = System.getProperty("bankname", "FirstRemote"); + // Name it that + Naming.rebind(name, bank); + // Tell the world we're up and running + System.out.println(name + " is open and ready for customers."); + } + catch (Exception e) { + System.err.println(e); + System.err.println("Usage: java [-Dbankname=] " + + "com.davidflanagan.examples.rmi.RemoteBankServer"); + System.exit(1); // Force exit because there may be RMI threads + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/security/Manifest.java b/src/main/java/com/davidflanagan/examples/security/Manifest.java new file mode 100644 index 0000000..c83fa46 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/security/Manifest.java @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.security; +import java.security.*; +import java.io.*; +import java.util.*; + +/** + * This program creates a manifest file for the specified files, or verifies + * an existing manifest file. By default the manifest file is named + * MANIFEST, but the -m option can be used to override this. The -v + * option specifies that the manifest should be verified. Verification is + * also the default option if no files are specified. + **/ +public class Manifest { + public static void main(String[] args) throws Exception { + // Set the default values of the command-line arguments + boolean verify = false; // Verify manifest or create one? + String manifestfile = "MANIFEST"; // Manifest file name + String digestAlgorithm = "MD5"; // Algorithm for message digests + String signername = null; // Signer. No sig. by default + String signatureAlgorithm = "DSA"; // Algorithm for digital sig. + String password = null; // Private keys are protected + File keystoreFile = null; // Where are keys stored + String keystoreType = null; // What kind of keystore + String keystorePassword = null; // How to access keystore + List filelist = new ArrayList(); // The files to digest + + // Parse the command-line arguments, overriding the defaults above + for(int i = 0; i < args.length; i++) { + if (args[i].equals("-v")) verify = true; + else if (args[i].equals("-m")) manifestfile = args[++i]; + else if (args[i].equals("-da")&& !verify) + digestAlgorithm = args[++i]; + else if (args[i].equals("-s")&& !verify) + signername = args[++i]; + else if (args[i].equals("-sa")&& !verify) + signatureAlgorithm = args[++i]; + else if (args[i].equals("-p")) + password = args[++i]; + else if (args[i].equals("-keystore")) + keystoreFile = new File(args[++i]); + else if (args[i].equals("-keystoreType")) + keystoreType = args[++i]; + else if (args[i].equals("-keystorePassword")) + keystorePassword = args[++i]; + + else if (!verify) filelist.add(args[i]); + else throw new IllegalArgumentException(args[i]); + } + + // If certain arguments weren't supplied, get default values. + if (keystoreFile == null) { + File dir = new File(System.getProperty("user.home")); + keystoreFile = new File(dir, ".keystore"); + } + if (keystoreType == null) keystoreType = KeyStore.getDefaultType(); + if (keystorePassword == null) keystorePassword = password; + + if (!verify && signername != null && password == null) { + System.out.println("Use -p to specify a password."); + return; + } + + // Get the keystore we'll use for signing or verifying signatures + // If no password was provided, then assume we won't be dealing with + // signatures, and skip the keystore. + KeyStore keystore = null; + if (keystorePassword != null) { + keystore = KeyStore.getInstance(keystoreType); + InputStream in = + new BufferedInputStream(new FileInputStream(keystoreFile)); + keystore.load(in, keystorePassword.toCharArray()); + } + + // If -v was specified or no file were given, verify a manifest + // Otherwise, create a new manifest for the specified files + if (verify || (filelist.size() == 0)) verify(manifestfile, keystore); + else create(manifestfile, digestAlgorithm, + signername, signatureAlgorithm, + keystore, password, filelist); + } + + /** + * This method creates a manifest file with the specified name, for + * the specified vector of files, using the named message digest + * algorithm. If signername is non-null, it adds a digital signature + * to the manifest, using the named signature algorithm. This method can + * throw a bunch of exceptions. + **/ + public static void create(String manifestfile, String digestAlgorithm, + String signername, String signatureAlgorithm, + KeyStore keystore, String password, + List filelist) + throws NoSuchAlgorithmException, InvalidKeyException, + SignatureException, KeyStoreException, + UnrecoverableKeyException, IOException + { + // For computing a signature, we have to process the files in a fixed, + // repeatable order, so sort them alphabetically. + Collections.sort(filelist); + int numfiles = filelist.size(); + + Properties manifest = new Properties(), metadata = new Properties(); + MessageDigest md = MessageDigest.getInstance(digestAlgorithm); + Signature signature = null; + byte[] digest; + + // If a signer name was specified, then prepare to sign the manifest + if (signername != null) { + // Get a Signature object + signature = Signature.getInstance(signatureAlgorithm); + + // Look up the private key of the signer from the keystore + PrivateKey key = (PrivateKey) + keystore.getKey(signername, password.toCharArray()); + + // No prepare to create a signature for the specified signer + signature.initSign(key); + } + + // Now, loop through the files, in a well-known alphabetical order + System.out.print("Computing message digests"); + for(int i = 0; i < numfiles; i++) { + String filename = (String)filelist.get(i); + // Compute the digest for each, and skip files that don't exist. + try { digest = getFileDigest(filename, md); } + catch (IOException e) { + System.err.println("\nSkipping " + filename + ": " + e); + continue; + } + // If we're computing a signature, use the bytes of the filename + // and of the digest as part of the data to sign. + if (signature != null) { + signature.update(filename.getBytes()); + signature.update(digest); + } + // Store the filename and the encoded digest bytes in the manifest + manifest.put(filename, hexEncode(digest)); + System.out.print('.'); + System.out.flush(); + } + + // If a signer was specified, compute signature for the manifest + byte[] signaturebytes = null; + if (signature != null) { + System.out.print("done\nComputing digital signature..."); + System.out.flush(); + + // Compute the digital signature by encrypting a message digest of + // all the bytes passed to the update() method using the private + // key of the signer. This is a time consuming operation. + signaturebytes = signature.sign(); + } + + // Tell the user what comes next + System.out.print("done\nWriting manifest..."); + System.out.flush(); + + // Store some metadata about this manifest, including the name of the + // message digest algorithm it uses + metadata.put("__META.DIGESTALGORITHM", digestAlgorithm); + // If we're signing the manifest, store some more metadata + if (signername != null) { + // Store the name of the signer + metadata.put("__META.SIGNER", signername); + // Store the name of the algorithm + metadata.put("__META.SIGNATUREALGORITHM", signatureAlgorithm); + // And generate the signature, encode it, and store it + metadata.put("__META.SIGNATURE", hexEncode(signaturebytes)); + } + + // Now, save the manifest data and the metadata to the manifest file + FileOutputStream f = new FileOutputStream(manifestfile); + manifest.store(f, "Manifest message digests"); + metadata.store(f, "Manifest metadata"); + System.out.println("done"); + } + + /** + * This method verifies the digital signature of the named manifest + * file, if it has one, and if that verification succeeds, it verifies + * the message digest of each file in filelist that is also named in the + * manifest. This method can throw a bunch of exceptions + **/ + public static void verify(String manifestfile, KeyStore keystore) + throws NoSuchAlgorithmException, SignatureException, + InvalidKeyException, KeyStoreException, IOException + { + Properties manifest = new Properties(); + manifest.load(new FileInputStream(manifestfile)); + String digestAlgorithm = + manifest.getProperty("__META.DIGESTALGORITHM"); + String signername = manifest.getProperty("__META.SIGNER"); + String signatureAlgorithm = + manifest.getProperty("__META.SIGNATUREALGORITHM"); + String hexsignature = manifest.getProperty("__META.SIGNATURE"); + + // Get a list of filenames in the manifest. + List files = new ArrayList(); + Enumeration names = manifest.propertyNames(); + while(names.hasMoreElements()) { + String s = (String)names.nextElement(); + if (!s.startsWith("__META")) files.add(s); + } + int numfiles = files.size(); + + // If we've got a signature but no keystore, warn the user + if (signername != null && keystore == null) + System.out.println("Can't verify digital signature without " + + "a keystore."); + + // If the manifest contained metadata about a digital signature, then + // verify that signature first + if (signername != null && keystore != null) { + System.out.print("Verifying digital signature..."); + System.out.flush(); + + // To verify the signature, we must process the files in exactly + // the same order we did when we created the signature. We + // guarantee this order by sorting the filenames. + Collections.sort(files); + + // Create a Signature object to do signature verification with. + // Initialize it with the signer's public key from the keystore + Signature signature = Signature.getInstance(signatureAlgorithm); + PublicKey publickey = + keystore.getCertificate(signername).getPublicKey(); + signature.initVerify(publickey); + + // Now loop through these files in their known sorted order For + // each one, send the bytes of the filename and of the digest to + // the signature object for use in computing the signature. It is + // important that this be done in exactly the same order when + // verifying the signature as it was done when creating the + // signature. + for(int i = 0; i < numfiles; i++) { + String filename = (String) files.get(i); + signature.update(filename.getBytes()); + signature.update(hexDecode(manifest.getProperty(filename))); + } + + // Now decode the signature read from the manifest file and pass + // it to the verify() method of the signature object. If the + // signature is not verified, print an error message and exit. + if (!signature.verify(hexDecode(hexsignature))) { + System.out.println("\nManifest has an invalid signature"); + System.exit(0); + } + + // Tell the user we're done with this lengthy computation + System.out.println("verified."); + } + + // Tell the user we're starting the next phase of verification + System.out.print("Verifying file message digests"); + System.out.flush(); + + // Get a MessageDigest object to compute digests + MessageDigest md = MessageDigest.getInstance(digestAlgorithm); + // Loop through all files + for(int i = 0; i < numfiles; i++) { + String filename = (String)files.get(i); + // Look up the encoded digest from the manifest file + String hexdigest = manifest.getProperty(filename); + // Compute the digest for the file. + byte[] digest; + try { digest = getFileDigest(filename, md); } + catch (IOException e) { + System.out.println("\nSkipping " + filename + ": " + e); + continue; + } + + // Encode the computed digest and compare it to the encoded digest + // from the manifest. If they are not equal, print an error + // message. + if (!hexdigest.equals(hexEncode(digest))) + System.out.println("\nFile '" + filename + + "' failed verification."); + + // Send one dot of output for each file we process. Since + // computing message digests takes some time, this lets the user + // know that the program is functioning and making progress + System.out.print("."); + System.out.flush(); + } + // And tell the user we're done with verification. + System.out.println("done."); + } + + /** + * This convenience method is used by both create() and verify(). It + * reads the contents of a named file and computes a message digest + * for it, using the specified MessageDigest object. + **/ + public static byte[] getFileDigest(String filename, MessageDigest md) + throws IOException { + // Make sure there is nothing left behind in the MessageDigest + md.reset(); + + // Create a stream to read from the file and compute the digest + DigestInputStream in = + new DigestInputStream(new FileInputStream(filename),md); + + // Read to the end of the file, discarding everything we read. + // The DigestInputStream automatically passes all the bytes read to + // the update() method of the MessageDigest + while(in.read(buffer) != -1) /* do nothing */ ; + + // Finally, compute and return the digest value. + return md.digest(); + } + + /** This static buffer is used by getFileDigest() above */ + public static byte[] buffer = new byte[4096]; + + /** This array is used to convert from bytes to hexadecimal numbers */ + static final char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + /** + * A convenience method to convert an array of bytes to a String. We do + * this simply by converting each byte to two hexadecimal digits. + * Something like Base 64 encoding is more compact, but harder to encode. + **/ + public static String hexEncode(byte[] bytes) { + StringBuffer s = new StringBuffer(bytes.length * 2); + for(int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + s.append(digits[(b& 0xf0) >> 4]); + s.append(digits[b& 0x0f]); + } + return s.toString(); + } + + /** + * A convenience method to convert in the other direction, from a string + * of hexadecimal digits to an array of bytes. + **/ + public static byte[] hexDecode(String s) throws IllegalArgumentException { + try { + int len = s.length(); + byte[] r = new byte[len/2]; + for(int i = 0; i < r.length; i++) { + int digit1 = s.charAt(i*2), digit2 = s.charAt(i*2 + 1); + if ((digit1 >= '0')&& (digit1 <= '9')) digit1 -= '0'; + else if ((digit1 >= 'a')&& (digit1 <= 'f')) digit1 -= 'a' - 10; + if ((digit2 >= '0')&& (digit2 <= '9')) digit2 -= '0'; + else if ((digit2 >= 'a')&& (digit2 <= 'f')) digit2 -= 'a' - 10; + r[i] = (byte)((digit1 << 4) + digit2); + } + return r; + } + catch (Exception e) { + throw new IllegalArgumentException("hexDecode(): invalid input"); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/security/SafeServer.java b/src/main/java/com/davidflanagan/examples/security/SafeServer.java new file mode 100644 index 0000000..13f7271 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/security/SafeServer.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.security; +import com.davidflanagan.examples.net.Server; +import java.io.*; +import java.net.*; +import java.security.*; + +/** + * This class is a program that uses the Server class defined in Chapter 5. + * Server would load arbitrary "Service" classes to provide services. + * This class is an alternative program to start up a Server in a similar + * way. The difference is that this one uses a SecurityManager and a + * ClassLoader to prevent the Service classes from doing anything damaging + * or malicious on the local system. This allows us to safely run Service + * classes that come from untrusted sources. + **/ +public class SafeServer { + public static void main(String[] args) { + try { + // Install a Security manager, if the user didn't already install + // one with the -Djava.security.manager argument + if (System.getSecurityManager() == null) { + System.out.println("Establishing a security manager"); + System.setSecurityManager(new SecurityManager()); + } + + // Create a Server object + Server server = new Server(null, 5); + + // Create the ClassLoader that we'll use to load Service classes. + // The classes should be stored in the JAR file or the directory + // specified as a URL by the first command-line argument + URL serviceURL = new URL(args[0]); + ClassLoader loader = + new URLClassLoader(new URL[] {serviceURL}); + + // Parse the argument list, which should contain Service name/port + // pairs. For each pair, load the named Service using the class + // loader, then instantiate it with newInstance(), then tell the + // server to start running it. + int i = 1; + while(i < args.length) { + // Dynamically load the Service class using the class loader + Class serviceClass = loader.loadClass(args[i++]); + // Dynamically instantiate the class. + Server.Service service = + (Server.Service)serviceClass.newInstance(); + int port = Integer.parseInt(args[i++]); // Parse the port # + server.addService(service, port); // Run service + } + } + catch (Exception e) { // Display a message if anything goes wrong + System.err.println(e); + System.err.println("Usage: java " + SafeServer.class.getName() + + " \n" + + "\t[ ... ]"); + System.exit(1); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/security/SafeServer.policy b/src/main/java/com/davidflanagan/examples/security/SafeServer.policy new file mode 100644 index 0000000..7e9e7c9 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/security/SafeServer.policy @@ -0,0 +1,39 @@ +// This file grants the SafeServer class the permissions it needs to load +// Service classes through a URLClassLoader, and grants the Service classes +// permission to read and write files in and beneath the directory specified +// by the service.tmp system property. Note that you'll need to edit the +// URL that specifies the location of the SafeServer class, and that for +// Windows systems, you'll need to replace "/" with "\\" + +// Grant permissions to the SafeServer class. +// Edit the directory for your system. +grant codeBase "file:/home/david/Books/JavaExamples2/Examples" { + // Allow the server to listen for and accept network connections + // from any host on any port > 1024 + permission java.net.SocketPermission "*:1024-", "listen,accept"; + + // Allow the server to create a class loader to load service classes + permission java.lang.RuntimePermission "createClassLoader"; + + // Give the server permission to read the directory that contains the + // service classes. If we were using a network URL instead of a file URL + // we'd need to add a SocketPermission instead of a FilePermission + permission java.io.FilePermission "${service.dir}/-", "read"; + + // The server cannot grant permissions to the Service classes unless it + // has those permissions itself. So we give the server these two Service + // permissions. + permission java.util.PropertyPermission "service.tmp", "read"; + permission java.io.FilePermission "${service.tmp}/-", "read,write"; +}; + +// Grant permissions to classes loaded from the directory specified by the +// service.dir system property. If we were using a network URL instead of a +// local file: URL, this line would have to be different. +grant codeBase "file:${service.dir}" { + // Services can read the system property "service.tmp" + permission java.util.PropertyPermission "service.tmp", "read"; + // And they can read and write files in the directory specified by + // that system property + permission java.io.FilePermission "${service.tmp}/-", "read,write"; +}; diff --git a/src/main/java/com/davidflanagan/examples/security/SecureService.java b/src/main/java/com/davidflanagan/examples/security/SecureService.java new file mode 100644 index 0000000..254dddd --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/security/SecureService.java @@ -0,0 +1,85 @@ +package com.davidflanagan.examples.security;/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +import com.davidflanagan.examples.net.*; // Note no package statement here. +import java.io.*; + +/** + * This is a demonstration service. It attempts to do things that may + * or may not be allowed by the security policy and reports the + * results of its attempts to the client. + **/ +public class SecureService implements Server.Service { + public void serve(InputStream i, OutputStream o) throws IOException { + PrintWriter out = new PrintWriter(o); + + // Try to install our own security manager. If we can do this, + // we can defeat any access control. + out.println("Trying to create and install a security manager..."); + try { + System.setSecurityManager(new SecurityManager()); + out.println("Success!"); + } + catch (Exception e) { out.println("Failed: " + e); } + + // Try to make the Server and the Java VM exit. + // This is a denial of service attack, and it should not succeed! + out.println(); + out.println("Trying to exit..."); + try { System.exit(-1); } + catch (Exception e) { out.println("Failed: " + e); } + + // The default system policy allows this property to be read + out.println(); + out.println("Attempting to find java version..."); + try { out.println(System.getProperty("java.version")); } + catch (Exception e) { out.println("Failed: " + e); } + + // The default system policy does not allow this property to be read + out.println(); + out.println("Attempting to find home directory..."); + try { out.println(System.getProperty("user.home")); } + catch (Exception e) { out.println("Failed: " + e); } + + // Our custom policy explicitly allows this property to be read + out.println(); + out.println("Attempting to read service.tmp property..."); + try { + String tmpdir = System.getProperty("service.tmp"); + out.println(tmpdir); + File dir = new File(tmpdir); + File f = new File(dir, "testfile"); + + // Check whether we've been given permission to write files to + // the tmpdir directory + out.println(); + out.println("Attempting to write a file in " + tmpdir + "..."); + try { + new FileOutputStream(f); + out.println("Opened file for writing: " + f); + } + catch (Exception e) { out.println("Failed: " + e); } + + // Check whether we've been given permission to read files from + // the tmpdir directory + out.println(); + out.println("Attempting to read from " + tmpdir + "..."); + try { + FileReader in = new FileReader(f); + out.println("Opened file for reading: " + f); + } + catch (Exception e) { out.println("Failed: " + e); } + } + catch (Exception e) { out.println("Failed: " + e); } + + // Close the Service sockets + out.close(); + i.close(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/security/Server.policy b/src/main/java/com/davidflanagan/examples/security/Server.policy new file mode 100644 index 0000000..40c3eda --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/security/Server.policy @@ -0,0 +1,8 @@ +// These lines grant permissions to any code loaded from the directory shown. +// Edit the directory to match the installation on your system. +// On Windows systems, change the forward slashes to double backslashes: "\\". +grant codeBase "file:/home/david/Books/JavaExamples2/Examples" { + // Allow the server to listen for and accept network connections + // from any host on any port > 1024 + permission java.net.SocketPermission "*:1024-", "listen,accept"; +}; diff --git a/src/main/java/com/davidflanagan/examples/security/TripleDES.java b/src/main/java/com/davidflanagan/examples/security/TripleDES.java new file mode 100644 index 0000000..eb0a98e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/security/TripleDES.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.security; +import javax.crypto.*; +import javax.crypto.spec.*; +import java.security.*; +import java.security.spec.*; +import java.io.*; + +/** + * This class defines methods for encrypting and decrypting using the Triple + * DES algorithm and for generating, reading and writing Triple DES keys. + * It also defines a main() method that allows these methods to be used + * from the command line. + **/ +public class TripleDES { + /** + * The program. The first argument must be -e, -d, or -g to encrypt, + * decrypt, or generate a key. The second argument is the name of a file + * from which the key is read or to which it is written for -g. The + * -e and -d arguments cause the program to read from standard input and + * encrypt or decrypt to standard output. + **/ + public static void main(String[] args) { + try { + // Check to see whether there is a provider that can do TripleDES + // encryption. If not, explicitly install the SunJCE provider. + try { Cipher c = Cipher.getInstance("DESede"); } + catch(Exception e) { + // An exception here probably means the JCE provider hasn't + // been permanently installed on this system by listing it + // in the $JAVA_HOME/jre/lib/security/java.security file. + // Therefore, we have to install the JCE provider explicitly. + System.err.println("Installing SunJCE provider."); + Provider sunjce = new com.sun.crypto.provider.SunJCE(); + Security.addProvider(sunjce); + } + + // This is where we'll read the key from or write it to + File keyfile = new File(args[1]); + + // Now check the first arg to see what we're going to do + if (args[0].equals("-g")) { // Generate a key + System.out.print("Generating key. This may take some time..."); + System.out.flush(); + SecretKey key = generateKey(); + writeKey(key, keyfile); + System.out.println("done."); + System.out.println("Secret key written to " + args[1] + + ". Protect that file carefully!"); + } + else if (args[0].equals("-e")) { // Encrypt stdin to stdout + SecretKey key = readKey(keyfile); + encrypt(key, System.in, System.out); + } + else if (args[0].equals("-d")) { // Decrypt stdin to stdout + SecretKey key = readKey(keyfile); + decrypt(key, System.in, System.out); + } + } + catch(Exception e) { + System.err.println(e); + System.err.println("Usage: java " + TripleDES.class.getName() + + " -d|-e|-g "); + } + } + + /** Generate a secret TripleDES encryption/decryption key */ + public static SecretKey generateKey() throws NoSuchAlgorithmException { + // Get a key generator for Triple DES (a.k.a DESede) + KeyGenerator keygen = KeyGenerator.getInstance("DESede"); + // Use it to generate a key + return keygen.generateKey(); + } + + /** Save the specified TripleDES SecretKey to the specified file */ + public static void writeKey(SecretKey key, File f) + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException + { + // Convert the secret key to an array of bytes like this + SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("DESede"); + DESedeKeySpec keyspec = + (DESedeKeySpec)keyfactory.getKeySpec(key, DESedeKeySpec.class); + byte[] rawkey = keyspec.getKey(); + + // Write the raw key to the file + FileOutputStream out = new FileOutputStream(f); + out.write(rawkey); + out.close(); + } + + /** Read a TripleDES secret key from the specified file */ + public static SecretKey readKey(File f) + throws IOException, NoSuchAlgorithmException, + InvalidKeyException, InvalidKeySpecException + { + // Read the raw bytes from the keyfile + DataInputStream in = new DataInputStream(new FileInputStream(f)); + byte[] rawkey = new byte[(int)f.length()]; + in.readFully(rawkey); + in.close(); + + // Convert the raw bytes to a secret key like this + DESedeKeySpec keyspec = new DESedeKeySpec(rawkey); + SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("DESede"); + SecretKey key = keyfactory.generateSecret(keyspec); + return key; + } + + /** + * Use the specified TripleDES key to encrypt bytes from the input stream + * and write them to the output stream. This method uses + * CipherOutputStream to perform the encryption and write bytes at the + * same time. + **/ + public static void encrypt(SecretKey key, InputStream in, OutputStream out) + throws NoSuchAlgorithmException, InvalidKeyException, + NoSuchPaddingException, IOException + { + // Create and initialize the encryption engine + Cipher cipher = Cipher.getInstance("DESede"); + cipher.init(Cipher.ENCRYPT_MODE, key); + + // Create a special output stream to do the work for us + CipherOutputStream cos = new CipherOutputStream(out, cipher); + + // Read from the input and write to the encrypting output stream + byte[] buffer = new byte[2048]; + int bytesRead; + while((bytesRead = in.read(buffer)) != -1) { + cos.write(buffer, 0, bytesRead); + } + cos.close(); + + // For extra security, don't leave any plaintext hanging around memory. + java.util.Arrays.fill(buffer, (byte) 0); + } + + /** + * Use the specified TripleDES key to decrypt bytes ready from the input + * stream and write them to the output stream. This method uses + * uses Cipher directly to show how it can be done without + * CipherInputStream and CipherOutputStream. + **/ + public static void decrypt(SecretKey key, InputStream in, OutputStream out) + throws NoSuchAlgorithmException, InvalidKeyException, IOException, + IllegalBlockSizeException, NoSuchPaddingException, + BadPaddingException + { + // Create and initialize the decryption engine + Cipher cipher = Cipher.getInstance("DESede"); + cipher.init(Cipher.DECRYPT_MODE, key); + + // Read bytes, decrypt, and write them out. + byte[] buffer = new byte[2048]; + int bytesRead; + while((bytesRead = in.read(buffer)) != -1) { + out.write(cipher.update(buffer, 0, bytesRead)); + } + + // Write out the final bunch of decrypted bytes + out.write(cipher.doFinal()); + out.flush(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/serialization/CompactIntList.java b/src/main/java/com/davidflanagan/examples/serialization/CompactIntList.java new file mode 100644 index 0000000..ce602e7 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/serialization/CompactIntList.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.serialization; +import java.io.*; + +/** + * This subclass of IntList assumes that most of the integers it contains are + * less than 32,000. It implements Externalizable so that it can define a + * compact serialization format that takes advantage of this fact. + **/ +public class CompactIntList extends IntList implements Externalizable { + /** + * This version number is here in case a later revision of this class wants + * to modify the externalization format, but still retain compatibility + * with externalized objects written by this version + **/ + static final byte version = 1; + + /** + * This method from the Externalizable interface is responsible for saving + * the complete state of the object to the specified stream. It can write + * anything it wants as long as readExternal() can read it. + **/ + public void writeExternal(ObjectOutput out) throws IOException { + if (data.length > size) resize(size); // Compact the array. + + out.writeByte(version); // Start with our version number. + out.writeInt(size); // Output the number of array elements + for(int i = 0; i < size; i++) { // Now loop through the array + int n = data[i]; // The array element to write + if ((n < Short.MAX_VALUE) && (n > Short.MIN_VALUE+1)) { + // If n fits in a short and is not Short.MIN_VALUE, then write + // it out as a short, saving ourselves two bytes + out.writeShort(n); + } + else { + // Otherwise write out the special value Short.MIN_VALUE to + // signal that the number does not fit in a short, and then + // output the number using a full 4 bytes, for 6 bytes total + out.writeShort(Short.MIN_VALUE); + out.writeInt(n); + } + } + } + + /** + * This Externalizable method is responsible for completely restoring the + * state of the object. A no-arg constructor will be called to re-create + * the object, and this method must read the state written by + * writeExternal() to restore the object's state. + **/ + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException + { + // Start by reading and verifying the version number. + byte v = in.readByte(); + if (v != version) + throw new IOException("CompactIntList: unknown version number"); + + // Read the number of array elements, and make array that big + int newsize = in.readInt(); + resize(newsize); + this.size = newsize; + + // Now read that many values from the stream + for(int i = 0; i < newsize; i++) { + short n = in.readShort(); + if (n != Short.MIN_VALUE) data[i] = n; + else data[i] = in.readInt(); + } + } + + /** A main() method to prove that it works */ + public static void main(String[] args) throws Exception { + CompactIntList list = new CompactIntList(); + for(int i = 0; i < 100; i++) list.add((int)(Math.random()*40000)); + CompactIntList copy = (CompactIntList)Serializer.deepclone(list); + if (list.equals(copy)) System.out.println("equal copies"); + Serializer.store(list, new File("compactintlist.ser")); + } +} diff --git a/src/main/java/com/davidflanagan/examples/serialization/IntList.java b/src/main/java/com/davidflanagan/examples/serialization/IntList.java new file mode 100644 index 0000000..ec65f38 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/serialization/IntList.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.serialization; +import java.io.*; + +/** + * A simple class that implements a growable array of ints, and knows + * how to serialize itself as efficiently as a non-growable array. + **/ +public class IntList implements Serializable { + protected int[] data = new int[8]; // An array to store the numbers. + protected transient int size = 0; // Index of next unused element of array + + /** Return an element of the array */ + public int get(int index) throws ArrayIndexOutOfBoundsException { + if (index >= size) throw new ArrayIndexOutOfBoundsException(index); + else return data[index]; + } + + /** Add an int to the array, growing the array if necessary */ + public void add(int x) { + if (data.length==size) resize(data.length*2); // Grow array if needed. + data[size++] = x; // Store the int in it. + } + + /** An internal method to change the allocated size of the array */ + protected void resize(int newsize) { + int[] newdata = new int[newsize]; // Create a new array + System.arraycopy(data, 0, newdata, 0, size); // Copy array elements. + data = newdata; // Replace old array + } + + /** Get rid of unused array elements before serializing the array */ + private void writeObject(ObjectOutputStream out) throws IOException { + if (data.length > size) resize(size); // Compact the array. + out.defaultWriteObject(); // Then write it out normally. + } + + /** Compute the transient size field after deserializing the array */ + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); // Read the array normally. + size = data.length; // Restore the transient field. + } + + /** + * Does this object contain the same values as the object o? + * We override this Object method so we can test the class. + **/ + public boolean equals(Object o) { + if (!(o instanceof IntList)) return false; + IntList that = (IntList) o; + if (this.size != that.size) return false; + for(int i = 0; i < this.size; i++) + if (this.data[i] != that.data[i]) return false; + return true; + } + + /** A main() method to prove that it works */ + public static void main(String[] args) throws Exception { + IntList list = new IntList(); + for(int i = 0; i < 100; i++) list.add((int)(Math.random()*40000)); + IntList copy = (IntList)Serializer.deepclone(list); + if (list.equals(copy)) System.out.println("equal copies"); + Serializer.store(list, new File("intlist.ser")); + } +} diff --git a/src/main/java/com/davidflanagan/examples/serialization/Serializer.java b/src/main/java/com/davidflanagan/examples/serialization/Serializer.java new file mode 100644 index 0000000..97164e7 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/serialization/Serializer.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.serialization; +import java.io.*; + +/** + * This class defines utility routines that use Java serialization. + **/ +public class Serializer { + /** + * Serialize the object o (and any Serializable objects it refers to) and + * store its serialized state in File f. + **/ + static void store(Serializable o, File f) throws IOException { + ObjectOutputStream out = // The class for serialization + new ObjectOutputStream(new FileOutputStream(f)); + out.writeObject(o); // This method serializes an object graph + out.close(); + } + + /** + * Deserialize the contents of File f and return the resulting object + **/ + static Object load(File f) throws IOException, ClassNotFoundException { + ObjectInputStream in = // The class for de-serialization + new ObjectInputStream(new FileInputStream(f)); + return in.readObject(); // This method deserializes an object graph + } + + /** + * Use object serialization to make a "deep clone" of the object o. + * This method serializes o and all objects it refers to, and then + * deserializes that graph of objects, which means that everything is + * copied. This differs from the clone() method of an object which is + * usually implemented to produce a "shallow" clone that copies references + * to other objects, instead of copying all referenced objects. + **/ + static Object deepclone(final Serializable o) + throws IOException, ClassNotFoundException + { + // Create a connected pair of "piped" streams. + // We'll write bytes to one, and them from the other one. + final PipedOutputStream pipeout = new PipedOutputStream(); + PipedInputStream pipein = new PipedInputStream(pipeout); + + // Now define an independent thread to serialize the object and write + // its bytes to the PipedOutputStream + Thread writer = new Thread() { + public void run() { + ObjectOutputStream out = null; + try { + out = new ObjectOutputStream(pipeout); + out.writeObject(o); + } + catch(IOException e) {} + finally { + try { out.close(); } catch (Exception e) {} + } + } + }; + writer.start(); // Make the thread start serializing and writing + + // Meanwhile, in this thread, read and deserialize from the piped + // input stream. The resulting object is a deep clone of the original. + ObjectInputStream in = new ObjectInputStream(pipein); + return in.readObject(); + } + + /** + * This is a simple serializable data structure that we use below for + * testing the methods above + **/ + public static class DataStructure implements Serializable { + String message; + int[] data; + DataStructure other; + public String toString() { + String s = message; + for(int i = 0; i < data.length; i++) + s += " " + data[i]; + if (other != null) s += "\n\t" + other.toString(); + return s; + } + } + + /** This class defines a main() method for testing */ + public static class Test { + public static void main(String[] args) + throws IOException, ClassNotFoundException + { + // Create a simple object graph + DataStructure ds = new DataStructure(); + ds.message = "hello world"; + ds.data = new int[] { 1, 2, 3, 4 }; + ds.other = new DataStructure(); + ds.other.message = "nested structure"; + ds.other.data = new int[] { 9, 8, 7 }; + + // Display the original object graph + System.out.println("Original data structure: " + ds); + + // Output it to a file + File f = new File("datastructure.ser"); + System.out.println("Storing to a file..."); + Serializer.store(ds, f); + + // Read it back from the file, and display it again + ds = (DataStructure) Serializer.load(f); + System.out.println("Read from the file: " + ds); + + // Create a deep clone and display that. After making the copy + // modify the original to prove that the clone is "deep". + DataStructure ds2 = (DataStructure) Serializer.deepclone(ds); + ds.other.message = null; ds.other.data = null; // Change original + System.out.println("Deep clone: " + ds2); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/servlet/Counter.java b/src/main/java/com/davidflanagan/examples/servlet/Counter.java new file mode 100644 index 0000000..2ad2909 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/Counter.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.servlet; +import javax.servlet.*; +import javax.servlet.http.*; +import java.io.*; +import java.util.*; + +/** + * This servlet maintains an arbitrary set of counter variables and increments + * and displays the value of one named counter each time it is invoked. It + * saves the state of the counters to a disk file, so the counts are not lost + * when the server shuts down. It is suitable for counting page hits, or any + * other type of event. It is not typically invoked directly, but is included + * within other pages, using JSP, SSI, or a RequestDispatcher + **/ +public class Counter extends HttpServlet { + HashMap counts; // A hash table: maps counter names to counts + File countfile; // The file that counts are saved in + long saveInterval; // How often (in ms) to save our state while running? + long lastSaveTime; // When did we last save our state? + + // This method is called when the web server first instantiates this + // servlet. It reads initialization parameters (which are configured + // at deployment time in the web.xml file), and loads the initial state + // of the counter variables from a file. + public void init() throws ServletException { + ServletConfig config = getServletConfig(); + try { + // Get the save file. + countfile = new File(config.getInitParameter("countfile")); + // How often should we save our state while running? + saveInterval = + Integer.parseInt(config.getInitParameter("saveInterval")); + // The state couldn't have changed before now. + lastSaveTime = System.currentTimeMillis(); + // Now read in the count data + loadState(); + } + catch(Exception e) { + // If something goes wrong, wrap the exception and rethrow it + throw new ServletException("Can't init Counter servlet: " + + e.getMessage(), e); + } + } + + // This method is called when the web server stops the servlet (which + // happens when the web server is shutting down, or when the servlet is + // not in active use.) This method saves the counts to a file so they + // can be restored when the servlet is restarted. + public void destroy() { + try { saveState(); } // Try to save the state + catch(Exception e) {} // Ignore any problems: we did the best we could + } + + // These constants define the request parameter and attribute names that + // the servlet uses to find the name of the counter to increment. + public static final String PARAMETER_NAME = "counter"; + public static final String ATTRIBUTE_NAME = + "com.davidflanagan.examples.servlet.Counter.counter"; + + /** + * This method is called when the servlet is invoked. It looks for a + * request parameter named "counter", and uses its value as the name of + * the counter variable to increment. If it doesn't find the request + * parameter, then it uses the URL of the request as the name of the + * counter. This is useful when the servlet is mapped to a URL suffix. + * This method also checks how much time has elapsed since it last saved + * its state, and saves the state again if necessary. This prevents it + * from losing too much data if the server crashes or shuts down without + * calling the destroy() method. + **/ + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException + { + // Get the name of the counter as a request parameter + String counterName = request.getParameter(PARAMETER_NAME); + + // If we didn't find it there, see if it was passed to us as a + // request attribute, which happens when the output of this servlet + // is included by another servlet + if (counterName == null) + counterName = (String) request.getAttribute(ATTRIBUTE_NAME); + + // If it wasn't a parameter or attribute, use the request URL. + if (counterName == null) counterName = request.getRequestURI(); + + Integer count; // What is the current count? + + // This block of code is synchronized because multiple requests may + // be running at the same time in different threads. Synchronization + // prevents them from updating the counts hashtable at the same time + synchronized(counts) { + // Get the counter value from the hashtable + count = (Integer)counts.get(counterName); + + // Increment the counter, or if it is new, log and start it at 1 + if (count != null) count = new Integer(count.intValue() + 1); + else { + // If this is a counter we haven't used before, send a message + // to the log file, just so we can track what we're counting + log("Starting new counter: " + counterName); + // Start counting at 1! + count = new Integer(1); + } + + // Store the incremented (or new) counter value into the hashtable + counts.put(counterName, count); + + // Check whether saveInterval milliseconds have elapsed since we + // last saved our state. If so, save it again. This prevents + // us from losing more than saveInterval ms of data, even if the + // server crashes unexpectedly. + if (System.currentTimeMillis() - lastSaveTime > saveInterval) { + saveState(); + lastSaveTime = System.currentTimeMillis(); + } + } // End of synchronized block + + // Finally, output the counter value. Since this servlet is usually + // included within the output of other servlets, we don't bother + // setting the content type. + PrintWriter out = response.getWriter(); + out.print(count); + } + + // The doPost method just calls doGet, so that this servlet can be + // included in pages that are loaded with POST requests + public void doPost(HttpServletRequest request,HttpServletResponse response) + throws IOException + { + doGet(request, response); + } + + // Save the state of the counters by serializing the hashtable to + // the file specified by the initialization parameter. + void saveState() throws IOException { + ObjectOutputStream out = new ObjectOutputStream( + new BufferedOutputStream(new FileOutputStream(countfile))); + out.writeObject(counts); // Save the hashtable to the stream + out.close(); // Always remember to close your files! + } + + // Load the initial state of the counters by de-serializing a hashtable + // from the file specified by the initialization parameter. If the file + // doesn't exist yet, then start with an empty hashtable. + void loadState() throws IOException { + if (!countfile.exists()) { + counts = new HashMap(); + return; + } + ObjectInputStream in = null; + try { + in = new ObjectInputStream( + new BufferedInputStream(new FileInputStream(countfile))); + counts = (HashMap) in.readObject(); + } + catch(ClassNotFoundException e) { + throw new IOException("Count file contains bad data: " + + e.getMessage()); + } + finally { + try { in.close(); } + catch (Exception e) {} + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/servlet/DecorBox.java b/src/main/java/com/davidflanagan/examples/servlet/DecorBox.java new file mode 100644 index 0000000..86e73c8 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/DecorBox.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.servlet; +import javax.servlet.jsp.*; // JSP classes +import javax.servlet.jsp.tagext.*; // Tag Library classes +import java.io.IOException; + +/** + * This Tag implementation is part of the "decor" tag library. It uses HTML + * tables to display a decorative box (with an optional title) around its + * content. The various properties correspond to the attributes supported by + * the tag. + **/ +public class DecorBox extends TagSupport { + // These fields contain values that control the appearance of the box + String align; // Alignment of the box + String title; // Title for the box + String titleColor; // Title foreground color + String titleAlign; // Title alignment relative to box + String color; // Box background color + String borderColor; // Border (and title background) color + String margin; // Pixels between box edge and content + String borderWidth; // Pixel width of the box border + + // The following property setter methods set the values of the fields above + // When a JSP page uses this tag any tag attribute settings will be + // translated into calls to these methods. + public void setAlign(String value) { align = value; } + public void setTitle(String value) { title = value; } + public void setTitleColor(String value) { titleColor = value; } + public void setTitleAlign(String value) { titleAlign = value; } + public void setColor(String value) { this.color = value; } + public void setBorderColor(String value) { borderColor = value; } + public void setMargin(String value) { margin = value; } + public void setBorderWidth(String value) { borderWidth = value; } + + /** + * This inherited method is always the first property setter invoked + * by the JSP container. We don't care about the page context here, but + * use this method to set the default values of the various properties. + * They are initialized here in case the JSP container wants to reuse + * this Tag object on multiple pages. + **/ + public void setPageContext(PageContext context) { + // Important! Let the superclass save the page context object. + // We'll need it in doStartTag() below. + super.setPageContext(context); + + // Now set default values for all the other properties + align = "center"; + title = null; + titleColor = "white"; + titleAlign = "left"; + color = "lightblue"; + borderColor = "black"; + margin = "20"; + borderWidth = "4"; + } + + /** + * This method is called when a tag is encountered. Any + * attributes will first be processed by calling the setter methods above. + **/ + public int doStartTag() throws JspException { + try { + // Get the output stream from the PageContext object, which + // will have been passed to the setPageContext() method. + JspWriter out = pageContext.getOut(); + + // Output the HTML tags necessary to display the box. The
+ // handles the alignment, and the creates the border. + out.print("
" + + "
"); + + // If there is a title, display it as a cell of the outer table + if (title != null) + out.print(""); + + // Now begin an inner table that has a different color than + // the border. + out.print("
" + + "" + + title + "
"); + } + catch (IOException e) { + // Unlike a PrintWriter, a JspWriter can throw IOExceptions + // We have to catch them and wrap them in a JSPException + throw new JspException(e.getMessage()); + } + + // This return value tells the JSP class to process the body of the tag + return EVAL_BODY_INCLUDE; + } + + /** + * This method is called when the closing tag is encountered + **/ + public int doEndTag() throws JspException { + // Try to output HTML to close the and
tags. + // Catch IOExceptions and rethrow them as JspExceptions + try { + JspWriter out = pageContext.getOut(); + out.println("
"); + } + catch (IOException e) { throw new JspException(e.getMessage()); } + + // This return value says to continue processing the JSP page. + return EVAL_PAGE; + } +} diff --git a/src/main/java/com/davidflanagan/examples/servlet/Hello.java b/src/main/java/com/davidflanagan/examples/servlet/Hello.java new file mode 100644 index 0000000..480767d --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/Hello.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.servlet; +import javax.servlet.*; // Basic servlet classes and interfaces +import javax.servlet.http.*; // HTTP specific servlet stuff +import java.io.*; // Servlets do IO and throw IOExceptions + +/** + * This simple servlet greets the user. It looks in the request and session + * objects in an attempt to greet the user by name. + **/ +public class Hello extends HttpServlet { + // This method is invoked when the servlet is the subject of an HTTP GET + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException + { + // See if the username is specified in the request + String name = request.getParameter("username"); + + // If not, look in the session object. The web server or servlet + // container performs session tracking automatically for the servlet, + // and associates a HttpSession object with each session. + if (name == null) + name = (String)request.getSession().getAttribute("username"); + + // If the username is not found in either place, use a default name. + if (name == null) name = "World"; + + // Specify the type of output we produce. If this servlet is + // included from within another servlet or JSP page, this setting + // will be ignored. + response.setContentType("text/html"); + + // Get an stream that we can write the output to + PrintWriter out = response.getWriter(); + + // And, finally, do our output. + out.println("Hello " + name + "!"); + } + + // This method is invoked when the servlet is the subject of an HTTP POST. + // It calls the doGet() method so that this servlet works correctly + // with either type of request. + public void doPost(HttpServletRequest request,HttpServletResponse response) + throws IOException + { + doGet(request, response); + } +} diff --git a/src/main/java/com/davidflanagan/examples/servlet/Logout.java b/src/main/java/com/davidflanagan/examples/servlet/Logout.java new file mode 100644 index 0000000..494d21f --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/Logout.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.servlet; +import javax.servlet.*; +import javax.servlet.http.*; + +/** + * This simple servlet ends the current session, and redirects the user's + * browser to a URL specified by the "page" request parameter. It should be + * suitable for use by any web application that requires the user to log in. + **/ +public class Logout extends HttpServlet { + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws java.io.IOException + { + // Destroy the user's session + request.getSession().invalidate(); + + // Figure out what to display next + String nextpage = request.getParameter("page"); + + // And redirect the user's browser to that page + response.sendRedirect(nextpage); + } + + // doPost just invokes doGet + public void doPost(HttpServletRequest request,HttpServletResponse response) + throws java.io.IOException + { + doGet(request, response); + } +} diff --git a/src/main/java/com/davidflanagan/examples/servlet/Query.java b/src/main/java/com/davidflanagan/examples/servlet/Query.java new file mode 100644 index 0000000..289b251 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/Query.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.servlet; +import javax.servlet.*; +import javax.servlet.http.*; +import java.sql.*; +import java.io.*; + +/** + * This class demonstrates how JDBC can be used within a servlet. It uses + * initialization parameters (which come from the web.xml configuration file) + * to create a single JDBC database connection, which is shared by all clients + * of the servlet. + ***/ +public class Query extends HttpServlet { + Connection db; // This is the shared JDBC database connection + + public void init() throws ServletException { + // Read initialization parameters from the web.xml file + ServletConfig config = getServletConfig(); + String driverClassName = config.getInitParameter("driverClassName"); + String url = config.getInitParameter("url"); + String username = config.getInitParameter("username"); + String password = config.getInitParameter("password"); + + // Use those init params to establish a connection to the database + // If anything goes wrong, log it, wrap the exception and re-throw it + try { + Class.forName(driverClassName); + db = DriverManager.getConnection(url, username, password); + } + catch (Exception e) { + log("Can't create DB connection", e); + throw new ServletException("Query: can't initialize: " + + e.getMessage(), e); + } + } + + /** Close the database connection when the servlet is unloaded */ + public void destroy() { + try { db.close(); } // Try to close the connection + catch (SQLException e) {} // Ignore errors; at least we tried! + } + + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + response.setContentType("text/html"); // We're outputting HTML + PrintWriter out = response.getWriter(); // Where to output it to + + // Output document header and a form for entering SQL queries + // When the form is submitted, this servlet is reloaded + out.println("DB Query\n" + + "

DB Query

\n" + + "
Query: " + + "
"); + + // See if a query was specified in this request. + String query = request.getParameter("q"); + if (query != null) { + // display the query text as a page heading + out.println("

" + query + "

"); + + // Now try to execute the query and display the results in a table + Statement statement = null; // An object to execute the query + try { + // Create a statement to use + statement = db.createStatement(); + // Use it to execute the specified query, and get result set + ResultSet results = statement.executeQuery(query); + // Ask for extra information about the results + ResultSetMetaData metadata = results.getMetaData(); + // How many columns are there in the results? + int numcols = metadata.getColumnCount(); + + // Begin a table, and output a header row of column names + out.println(""); + for(int i = 0; i < numcols; i++) + out.print(""); + out.println(""); + + // Now loop through the "rows" of the result set + while(results.next()) { + // For each row, display the the values for each column + out.print(""); + for(int i = 0; i < numcols; i++) + out.print(""); + out.println(""); + } + out.println("
" + metadata.getColumnLabel(i+1) + "
" + results.getObject(i+1) + "
"); // end the table + + } + catch (SQLException e) { + // If anything goes wrong (usually a SQL error) display the + // error to the user so they can correct it. + out.println("SQL Error: " + e.getMessage()); + } + finally { // Whatever happens, always close the Statement object + try { statement.close(); } + catch(Exception e) {} + } + } + + // Now, display the number of hits on this page by invoking the + // Counter servlet and including its output in this page. + // This is done with a RequestDispatcher object. + RequestDispatcher dispatcher = + request.getRequestDispatcher("/servlet/counter"); + if (dispatcher != null) { + out.println("
Page hits:"); + // Add a request attribute that tells the servlet what to count. + // Use the attribute name defined by the Counter servlet, and + // use the name of this class as a unique counter name. + request.setAttribute(Counter.ATTRIBUTE_NAME,Query.class.getName()); + // Tell the dispatcher to invoke its servlet and include the output + dispatcher.include(request, response); + } + + // Finally, end the HTML output + out.println(""); + } +} diff --git a/src/main/java/com/davidflanagan/examples/servlet/UserBean.java b/src/main/java/com/davidflanagan/examples/servlet/UserBean.java new file mode 100644 index 0000000..7cd0f88 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/UserBean.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.servlet; +import javax.servlet.http.*; + +/** + * This class is a simple non-visual JavaBean that defines properties that can + * be used from a JSP page with the , and + * tags. These JSP tags allow significant chunks of Java + * code to be taken out of JSP pages and placed in Java files, where they are + * easier to read, edit, and maintain. This example only defines a few + * trivial properties, but the class could do much more. + * + * This class is instantiated by portal.jsp and is bound in the Session object. + * Therefore, it implements HttpSessionBindingListener so that it is notified + * when the session is terminated (when the user logs out or the session + * times out). + **/ +public class UserBean implements HttpSessionBindingListener { + String username; // The name of the user we represent + String favorite = colors[0]; // The user's favorite color, with a default + static final String[] colors = { "gray", "lightblue", "pink", "yellow" }; + + // These are the getter and setter methods for the userName property + // In a real program, setUserName() would probably look up information + // about the user in a database of some kind. + public String getUserName() { return username; } + public void setUserName(String username) { this.username = username; } + + // These are the getter and setter methods for the favoriteColor property + public String getFavoriteColor() { return favorite; } + public void setFavoriteColor(String favorite) { this.favorite = favorite; } + + // Return a list of colors the user is allowed to choose from + public String[] getColorChoices() { return colors; } + + // This is a getter method for the "customContent" property. In a more + // sophisticated example, this method might query a database and return + // current news clippings or stock quotes for the user. Not here, though. + public String getCustomContent() { + return "Your name backwards is: " + + new StringBuffer(username).reverse() + ""; + } + + + // This method implements HttpSessionBindingListener. If an instance of + // this class is bound in a HttpSession object, then this method will + // be invoked when the instance becomes unbound, which typically happens + // when the session is invalidated because the user logged out or + // was inactive for too long. In a real example, this method would + // probably save information about the user to a file or database. + public void valueUnbound(HttpSessionBindingEvent e) { + System.out.println(username + " logged out or timed out." + + " Favorite color: " + favorite); + } + + // Part of HttpSessionBindingListener; we don't care about it here + public void valueBound(HttpSessionBindingEvent e) {} +} diff --git a/src/main/java/com/davidflanagan/examples/servlet/WEB-INF/tlds/decor_0_1.tld b/src/main/java/com/davidflanagan/examples/servlet/WEB-INF/tlds/decor_0_1.tld new file mode 100644 index 0000000..00ad9c3 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/WEB-INF/tlds/decor_0_1.tld @@ -0,0 +1,71 @@ + + + + + + + + 0.1 + 1.1 + decor + + http://www.davidflanagan.com/tlds/decor_0_1.tld + + + A simple tag library for decorative HTML output + + + + + + box + com.davidflanagan.examples.servlet.DecorBox + Display a colored box with a border + + + + + + align + false + true + + + color + false + true + + + borderColor + false + true + + + margin + false + true + + + borderWidth + false + true + + + title + false + true + + + titleColor + false + true + + + titleAlign + false + true + + + diff --git a/src/main/java/com/davidflanagan/examples/servlet/WEB-INF/web.xml b/src/main/java/com/davidflanagan/examples/servlet/WEB-INF/web.xml new file mode 100644 index 0000000..5f013d6 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/WEB-INF/web.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + hello + com.davidflanagan.examples.servlet.Hello + + + + + + counter + com.davidflanagan.examples.servlet.Counter + + countfile + /tmp/counts.ser + + + saveInterval + 30000 + + + + + query + com.davidflanagan.examples.servlet.Query + + + driverClassName + org.gjt.mm.mysql.Driver + + + url + jdbc:mysql://dbserver.my.domain.com/dbname + + + username + david + + + password + secret + + + + + + + + + queryOtherDatabase + com.davidflanagan.examples.servlet.Query + + + + + logout + com.davidflanagan.examples.servlet.Logout + + + + + + + counter + *.count + + + + + 15 + + + + + + http://www.davidflanagan.com/tlds/decor_0_1.tld + + tlds/decor_0_1.tld + + diff --git a/src/main/java/com/davidflanagan/examples/servlet/forcelogin.jsp b/src/main/java/com/davidflanagan/examples/servlet/forcelogin.jsp new file mode 100644 index 0000000..5c4d611 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/forcelogin.jsp @@ -0,0 +1,36 @@ +<%-- + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. +--%> +<%--forcelogin.jsp + This is a fragment of JSP code. It cannot stand alone, but is + intended to be included within other JSP pages. It looks for a + "username" attribute in the Session object, and if it does not find + one, it forwards the request to the login.jsp page, which will + authenticate the user and store their name in the session object. + This code sends a "nextpage" request parameter to the login.jsp, so + that the login page knows where to return to once the user has + successfully logged in. Note that request parameters passed to this + page are not preserved when the login page redirects back to this page. +--%> +<% +// Check whether the username is already defined in the Session object +String _username = (String) session.getAttribute("username"); +if (_username == null) { // If the user has not already logged in + String _page = request.getRequestURI(); +%> +<%-- Invoke the login.jsp page. The tag invokes the --%> +<%-- page directly on the server side without sending a redirect --%> +<%-- to the client The tags specify request parameters --%> +<%-- that are passed to login.jsp --%> + + + + +<%}%> diff --git a/src/main/java/com/davidflanagan/examples/servlet/login.jsp b/src/main/java/com/davidflanagan/examples/servlet/login.jsp new file mode 100644 index 0000000..17ab3cb --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/login.jsp @@ -0,0 +1,122 @@ +<%-- + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. +--%> +<%--login.jsp + The next two lines are JSP directives. The page directive tells the JSP + compiler that this page contains Java (instead of JavaScript e.g.) code and + outputs HTML (instead of XML, e.g.). The second directive tells the JSP + compiler that this page uses a "tag library" with the specified unique + identifier and whose tags are prefixed (on this page) by the word "decor". +--%> +<%@page language='java' contentType='text/html'%> +<%@taglib uri='http://www.davidflanagan.com/tlds/decor_0_1.tld' + prefix='decor'%> + +<%-- + The code below is in a <%!...%> declaration block. When this JSP page + is compiled to a servlet, this code is used to define members of the + Servlet class. +--%> + +<%! // Begin declaration block + +// This method does very simple password verification. In a real application, +// this method would probably check a database of passwords. +public boolean verify(String username, String password) { + // Accept any username as long as the password is "java" + return ((username!=null) && (password!=null) && password.equals("java")); +} + +%> <%-- End declaration block --%> + +<%-- + The next block of code is between <% and %>, which mark it as a "scriptlet". + When the JSP page is compiled, this code becomes part of the service() + method of the servlet. Scriptlet blocks are intermixed with HTML tags which + are also compiled into the service() method and are output literally by the + servlet. Notice how this scriptlet ends in the middle of an else block. + The HTML tags that follow it form part of the else{} block, and the block is + closed in a scriptlet that comes later in the file. +--%> + +<% // Begin scriptlet +// This request parameter is required. It specifies what should be +// displayed if the login attempt is successful +String nextPage = request.getParameter("nextpage"); + +// This request parameter specifies a title for the login page +String title = request.getParameter("title"); +if (title == null) title = "Please Log In"; // If not specified, use a default + +// Look for username and password parameters in the request +String username = request.getParameter("username"); +String password = request.getParameter("password"); + +// If the username and password are defined and valid, then store +// the username in the session, and redirect to the specified page +// We do this without displaying any content of our own. +if ((username != null) && (password != null) && verify(username, password)) { + session.setAttribute("username", username); + response.sendRedirect(nextPage); +} +else { + // Otherwise, we're going to have to display the login screen. + // If the username and password properties are totally undefined, + // then this is the first time, and all we display is the screen. + // Otherwise, if they are defined, then we've just had a failed login + // attempt, so display an additional "please try again" message. + String message = ""; + if ((username != null) || (password != null)) { + message = "Invalid username or password. Please try again"; + } +%> + + <%-- This is the body of the else block started above. It displays --%> + <%-- the login page. It is straight HTML with only a few Java --%> + <%-- expressions contained in <%= %> tags. It also contains tags --%> + <%-- from a custom tag library, the subject of a later example --%> + Login + +


<%-- Space down from the top of the page a bit --%> + <%-- A custom tag: display a decorative box --%> + +
<%-- Center everything inside the box --%> + <%-- Display the login title and optional error message --%> +

<%=title%>

+ <%=message%> + <%-- Now display an HTML form for the user to enter login information --%> +
+ <%-- Use a table to make the login form look nice --%> + <%-- First row: username --%> + + + <%-- Second row: password --%> + + + <%-- Third row: login button --%> + + +
+ Username: +
+ Password: +
+ +
+ <%-- The form must also include some hidden fields so this page --%> + <%-- can pass the nextpage and title parameters back to itself --%> + + +
+
+
<%-- End of the custom box tag --%> + <%-- End of the HTML output --%> +<% +} // This is one final scriptlet to close the else block started above +%> diff --git a/src/main/java/com/davidflanagan/examples/servlet/makewar.sh b/src/main/java/com/davidflanagan/examples/servlet/makewar.sh new file mode 100644 index 0000000..3ad3de4 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/makewar.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +# Delete all Java class files, and recompile everything +echo "Recompiling Java classes..." +rm *.class +javac *.java + +# Create a temporary directory structure for the web application +echo "Creating WAR file..." +mkdir temp +mkdir temp/WEB-INF +mkdir temp/WEB-INF/tlds +mkdir temp/WEB-INF/classes +mkdir temp/WEB-INF/classes/com +mkdir temp/WEB-INF/classes/com/davidflanagan +mkdir temp/WEB-INF/classes/com/davidflanagan/examples +mkdir temp/WEB-INF/classes/com/davidflanagan/examples/servlet + +# Now copy our files into those directories +cp *.jsp temp # JSP files go at the top level +cp WEB-INF/web.xml temp/WEB-INF # Configuration files in WEB-INF/ +cp WEB-INF/tlds/decor_0_1.tld temp/WEB-INF/tlds +# Java class files go under WEB-INF/classes +cp *.class temp/WEB-INF/classes/com/davidflanagan/examples/servlet + +# At this point, the temporary directory contains all our files, so +# we can jar them up into a WAR file with the name javaexamples2.war +# This WAR file can now simply be dropped into the Tomcat webapps/ directory. +cd temp +jar cMf ../javaexamples2.war * + +# Now delete the temporary directory hierarchy +cd .. +rm -rf temp diff --git a/src/main/java/com/davidflanagan/examples/servlet/portal.jsp b/src/main/java/com/davidflanagan/examples/servlet/portal.jsp new file mode 100644 index 0000000..10c5326 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/servlet/portal.jsp @@ -0,0 +1,132 @@ +<%-- + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. +--%> +<%@page language='java' contentType='text/html'%> <%-- On every JSP page --%> + +<%-- Specify a tag library to use in this file --%> +<%@taglib uri='http://www.davidflanagan.com/tlds/decor_0_1.tld' + prefix='decor'%> + +<%-- Include the JSP code from forcelogin.jsp when this page is compiled --%> +<%-- The included file checks if the user is logged in, and if not, --%> +<%-- forwards to the login.jsp page to get a username and password. This --%> +<%-- ensures that the 'username' attribute is defined in the session. --%> +<%@include file='forcelogin.jsp'%> + +<%-- Declare a new variable named 'user'. It is an instance of UserBean --%> +<%-- and it is associated with the session object. If this is a new --%> +<%-- session, then instantiate the bean and set its 'userName' property --%> +<%-- to the value of the username from the session. --%> + + + + +<%-- Each time this page is displayed, set the 'favoriteColor' property of --%> +<%-- the 'user' bean. Since no 'value' parameter is supplied, set the --%> +<%-- property to the 'favoriteColor' parameter of the incoming request, if --%> +<%-- there is a parameter of that name; otherwise don't set the property. --%> + + +<%-- Begin to output the page. Note the use of the JSP to include a Java --%> +<%-- expression within the HTML tag. This is an explicit use of --%> +<%-- 'user' bean declared above. --%> +The Demo Portal + + +<%-- Greet the user by invoking the hello servlet and including its output --%> +<%-- here. The hello servlet expects to find the username in the session --%> +<%-- where the login.jsp page stored it. Note the difference between --%> +<%-- this runtime jsp:include tag and the @include directive used above --%> +<%-- to include the forcelogin.jsp file at compile-time. --%> +
+

+
+ +<%-- Display a box using our 'decor' tag library, and as its content --%> +<%-- use the value of the 'customContent' property of the 'user' bean --%> +<%-- Hopefully, the bean will provide interesting content tailored --%> +<%-- to the specified user --%> + + + + +<%-- Begin another box --%> +<%-- The content of this box is a table with 2 cells side-by-side --%> +

+ + +<%-- This is the second cell of the table. It contains a form for --%> +<%-- selecting a favorite color. Note the technique used to output the --%> +<%--
+<%-- The first cell is another box. It displays some text and the value--%> +<%-- of the 'favoriteColor' property of the 'user' bean --%> + +Your favorite color is: + + + +
<%-- Begin the form --%> + object --%> +<% // Begin a Java scriptlet +// Ask the bean for the list of color choices, and the user's favorite +String[] colors = user.getColorChoices(); +String favorite = user.getFavoriteColor(); +// Now start looping through the list of colors. The body of this loop +// is the HTML and JSP code below. +for(int i = 0; i < colors.length; i++) { +%> <%-- End the scriptlet --%> + <%-- Each time through the loop, we'll output an +<%}%> <%-- End the for loop --%> + <%-- End the <%-- Submit button for the form --%> +
<%-- End the form--%> +
<%-- End of 2nd cell and the table --%> +
<%-- End of the box --%> + +<%-- Begin a new box. This one also contains a 2-cell table --%> +
+ +
+ +<%-- The first table cell is a simple form that allows the user to logout --%> +<%-- See Logout.java +for details --%> +
+ + +
+ +<%-- The second item in the box is another box that displays counts --%> +<%-- We use jsp:include twice more to invoke the Counter servlet. --%> +<%-- Note that for the 2nd inclusion, we rely on the fact that the web.xml --%> +<%-- file maps any URL ending in ".count" to the Counter servlet --%> +
+ +You have visited + + + +times. The portal has been visited + +times. + +
+
diff --git a/src/main/java/com/davidflanagan/examples/sql/APIDB.props b/src/main/java/com/davidflanagan/examples/sql/APIDB.props new file mode 100644 index 0000000..02ef9db --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/sql/APIDB.props @@ -0,0 +1,4 @@ +driver=org.gjt.mm.mysql.Driver +database=jdbc:mysql://dbserver.mydomain.org/APIDB +user=my_user_name +password=my_secret_password diff --git a/src/main/java/com/davidflanagan/examples/sql/BankDB.props b/src/main/java/com/davidflanagan/examples/sql/BankDB.props new file mode 100644 index 0000000..9f3c096 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/sql/BankDB.props @@ -0,0 +1,5 @@ +driver: postgresql.Driver +database: jdbc:postgresql:bank +user: david +password: + diff --git a/src/main/java/com/davidflanagan/examples/sql/ExecuteSQL.java b/src/main/java/com/davidflanagan/examples/sql/ExecuteSQL.java new file mode 100644 index 0000000..44af87a --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/sql/ExecuteSQL.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.sql; +import java.sql.*; +import java.io.*; + +/** + * A general-purpose SQL interpreter program. + **/ +public class ExecuteSQL { + public static void main(String[] args) { + Connection conn = null; // Our JDBC connection to the database server + try { + String driver = null, url = null, user = "", password = ""; + + // Parse all the command-line arguments + for(int n = 0; n < args.length; n++) { + if (args[n].equals("-d")) driver = args[++n]; + else if (args[n].equals("-u")) user = args[++n]; + else if (args[n].equals("-p")) password = args[++n]; + else if (url == null) url = args[n]; + else throw new IllegalArgumentException("Unknown argument."); + } + + // The only required argument is the database URL. + if (url == null) + throw new IllegalArgumentException("No database specified"); + + // If the user specified the classname for the DB driver, load + // that class dynamically. This gives the driver the opportunity + // to register itself with the DriverManager. + if (driver != null) Class.forName(driver); + + // Now open a connection the specified database, using the + // user-specified username and password, if any. The driver + // manager will try all of the DB drivers it knows about to try to + // parse the URL and connect to the DB server. + conn = DriverManager.getConnection(url, user, password); + + // Now create the statement object we'll use to talk to the DB + Statement s = conn.createStatement(); + + // Get a stream to read from the console + BufferedReader in = + new BufferedReader(new InputStreamReader(System.in)); + + // Loop forever, reading the user's queries and executing them + while(true) { + System.out.print("sql> "); // prompt the user + System.out.flush(); // make the prompt appear now. + String sql = in.readLine(); // get a line of input from user + + // Quit when the user types "quit". + if ((sql == null) || sql.equals("quit")) break; + + // Ignore blank lines + if (sql.length() == 0) continue; + + // Now, execute the user's line of SQL and display results. + try { + // We don't know if this is a query or some kind of + // update, so we use execute() instead of executeQuery() + // or executeUpdate() If the return value is true, it was + // a query, else an update. + boolean status = s.execute(sql); + + // Some complex SQL queries can return more than one set + // of results, so loop until there are no more results + do { + if (status) { // it was a query and returns a ResultSet + ResultSet rs = s.getResultSet(); // Get results + printResultsTable(rs, System.out); // Display them + } + else { + // If the SQL command that was executed was some + // kind of update rather than a query, then it + // doesn't return a ResultSet. Instead, we just + // print the number of rows that were affected. + int numUpdates = s.getUpdateCount(); + System.out.println("Ok. " + numUpdates + + " rows affected."); + } + + // Now go see if there are even more results, and + // continue the results display loop if there are. + status = s.getMoreResults(); + } while(status || s.getUpdateCount() != -1); + } + // If a SQLException is thrown, display an error message. + // Note that SQLExceptions can have a general message and a + // DB-specific message returned by getSQLState() + catch (SQLException e) { + System.err.println("SQLException: " + e.getMessage()+ ":" + + e.getSQLState()); + } + // Each time through this loop, check to see if there were any + // warnings. Note that there can be a whole chain of warnings. + finally { // print out any warnings that occurred + SQLWarning w; + for(w=conn.getWarnings(); w != null; w=w.getNextWarning()) + System.err.println("WARNING: " + w.getMessage() + + ":" + w.getSQLState()); + } + } + } + // Handle exceptions that occur during argument parsing, database + // connection setup, etc. For SQLExceptions, print the details. + catch (Exception e) { + System.err.println(e); + if (e instanceof SQLException) + System.err.println("SQL State: " + + ((SQLException)e).getSQLState()); + System.err.println("Usage: java ExecuteSQL [-d ] " + + "[-u ] [-p ] "); + } + + // Be sure to always close the database connection when we exit, + // whether we exit because the user types 'quit' or because of an + // exception thrown while setting things up. Closing this connection + // also implicitly closes any open statements and result sets + // associated with it. + finally { + try { conn.close(); } catch (Exception e) {} + } + } + + /** + * This method attempts to output the contents of a ResultSet in a + * textual table. It relies on the ResultSetMetaData class, but a fair + * bit of the code is simple string manipulation. + **/ + static void printResultsTable(ResultSet rs, OutputStream output) + throws SQLException + { + // Set up the output stream + PrintWriter out = new PrintWriter(output); + + // Get some "meta data" (column names, etc.) about the results + ResultSetMetaData metadata = rs.getMetaData(); + + // Variables to hold important data about the table to be displayed + int numcols = metadata.getColumnCount(); // how many columns + String[] labels = new String[numcols]; // the column labels + int[] colwidths = new int[numcols]; // the width of each + int[] colpos = new int[numcols]; // start position of each + int linewidth; // total width of table + + // Figure out how wide the columns are, where each one begins, + // how wide each row of the table will be, etc. + linewidth = 1; // for the initial '|'. + for(int i = 0; i < numcols; i++) { // for each column + colpos[i] = linewidth; // save its position + labels[i] = metadata.getColumnLabel(i+1); // get its label + // Get the column width. If the db doesn't report one, guess + // 30 characters. Then check the length of the label, and use + // it if it is larger than the column width + int size = metadata.getColumnDisplaySize(i+1); + if (size == -1) size = 30; // Some drivers return -1... + if (size > 500) size = 30; // Don't allow unreasonable sizes + int labelsize = labels[i].length(); + if (labelsize > size) size = labelsize; + colwidths[i] = size + 1; // save the column the size + linewidth += colwidths[i] + 2; // increment total size + } + + // Create a horizontal divider line we use in the table. + // Also create a blank line that is the initial value of each + // line of the table + StringBuffer divider = new StringBuffer(linewidth); + StringBuffer blankline = new StringBuffer(linewidth); + for(int i = 0; i < linewidth; i++) { + divider.insert(i, '-'); + blankline.insert(i, " "); + } + // Put special marks in the divider line at the column positions + for(int i=0; i blen) slen = blen-pos; // does it fit? + for(int i = 0; i < slen; i++) // copy string into buffer + b.setCharAt(pos+i, s.charAt(i)); + } +} diff --git a/src/main/java/com/davidflanagan/examples/sql/GetDBInfo.java b/src/main/java/com/davidflanagan/examples/sql/GetDBInfo.java new file mode 100644 index 0000000..de24bf1 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/sql/GetDBInfo.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.sql; +import java.sql.*; +import java.util.Properties; + +/** + * This class uses the DatabaseMetaData class to obtain information about + * the database, the JDBC driver, and the tables in the database, or about + * the columns of a named table. + **/ +public class GetDBInfo { + public static void main(String[] args) { + Connection c = null; // The JDBC connection to the database server + try { + // Look for the properties file DB.props in the same directory as + // this program. It will contain default values for the various + // parameters needed to connect to a database + Properties p = new Properties(); + try { p.load(GetDBInfo.class.getResourceAsStream("DB.props")); } + catch (Exception e) {} + + // Get default values from the properties file + String driver = p.getProperty("driver"); // Driver class name + String server = p.getProperty("server", ""); // JDBC URL for server + String user = p.getProperty("user", ""); // db user name + String password = p.getProperty("password", ""); // db password + + // These variables don't have defaults + String database = null; // The db name (appended to server URL) + String table = null; // The optional name of a table in the db + + // Parse the command-line args to override the default values above + for(int i = 0; i < args.length; i++) { + if (args[i].equals("-d")) driver = args[++i]; //-d + else if (args[i].equals("-s")) server = args[++i];//-s + else if (args[i].equals("-u")) user = args[++i]; //-u + else if (args[i].equals("-p")) password = args[++i]; + else if (database == null) database = args[i]; // + else if (table == null) table = args[i]; // + else throw new IllegalArgumentException("Unknown argument: " + +args[i]); + } + + // Make sure that at least a server or a database were specified. + // If not, we have no idea what to connect to, and cannot continue. + if ((server.length() == 0) && (database.length() == 0)) + throw new IllegalArgumentException("No database specified."); + + // Load the db driver, if any was specified. + if (driver != null) Class.forName(driver); + + // Now attempt to open a connection to the specified database on + // the specified server, using the specified name and password + c = DriverManager.getConnection(server+database, user, password); + + // Get the DatabaseMetaData object for the connection. This is the + // object that will return us all the data we're interested in here + DatabaseMetaData md = c.getMetaData(); + + // Display information about the server, the driver, etc. + System.out.println("DBMS: " + md.getDatabaseProductName() + + " " + md.getDatabaseProductVersion()); + System.out.println("JDBC Driver: " + md.getDriverName() + + " " + md.getDriverVersion()); + System.out.println("Database: " + md.getURL()); + System.out.println("User: " + md.getUserName()); + + // Now, if the user did not specify a table, then display a list of + // all tables defined in the named database. Note that tables are + // returned in a ResultSet, just like query results are. + if (table == null) { + System.out.println("Tables:"); + ResultSet r = md.getTables("", "", "%", null); + while(r.next()) System.out.println("\t" + r.getString(3)); + } + + // Otherwise, list all columns of the specified table. + // Again, information about the columns is returned in a ResultSet + else { + System.out.println("Columns of " + table + ": "); + ResultSet r = md.getColumns("", "", table, "%"); + while(r.next()) + System.out.println("\t" + r.getString(4) + " : " + + r.getString(6)); + } + } + // Print an error message if anything goes wrong. + catch (Exception e) { + System.err.println(e); + if (e instanceof SQLException) + System.err.println(((SQLException)e).getSQLState()); + System.err.println("Usage: java GetDBInfo [-d ]\n" + + "\t[-u ] [-p ] "); + } + // Always remember to close the Connection object when we're done! + finally { + try { c.close(); } catch (Exception e) {} + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/sql/LookupAPI.java b/src/main/java/com/davidflanagan/examples/sql/LookupAPI.java new file mode 100644 index 0000000..bd673a0 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/sql/LookupAPI.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.sql; +import java.sql.*; +import java.io.FileInputStream; +import java.util.Properties; + +/** + * This program uses the database created by MakeAPIDB. It opens a connection + * to a database using the same property file used by MakeAPIDB. Then it + * queries that database in several interesting ways to obtain useful + * information about Java APIs. It can be used to look up the fully-qualified + * name of a member, class, or package, or it can be used to list the members + * of a class or package. + **/ +public class LookupAPI { + public static void main(String[] args) { + Connection c = null; // JDBC connection to the database + try { + // Some default values + String target = null; // The name to look up + boolean list = false; // List members or lookup name? + String propfile = "APIDB.props"; // The file of db parameters + + // Parse the command-line arguments + for(int i = 0; i < args.length; i++) { + if (args[i].equals("-l")) list = true; + else if (args[i].equals("-p")) propfile = args[++i]; + else if (target != null) + throw new IllegalArgumentException("Unexpected argument: " + + args[i]); + else target = args[i]; + } + if (target == null) + throw new IllegalArgumentException("No target specified"); + + // Now determine the values needed to set up the database + // connection The program attempts to read a property file + // named "APIDB.props", or optionally specified with the + // -p argument. This property file may contain "driver", + // "database", "user", and "password" properties that + // specify the necessary values for connecting to the db. + // If the properties file does not exist, or does not + // contain the named properties, defaults will be used. + Properties p = new Properties(); // Empty properties + try { p.load(new FileInputStream(propfile)); } // Try to load props + catch (Exception e) {} + + // Read values from Properties file + String driver = p.getProperty("driver"); + String database = p.getProperty("database"); + String user = p.getProperty("user", ""); + String password = p.getProperty("password", ""); + + // The driver and database properties are mandatory + if (driver == null) + throw new IllegalArgumentException("No driver specified!"); + if (database == null) + throw new IllegalArgumentException("No database specified!"); + + // Load the database driver + Class.forName(driver); + + // And set up a connection to the specified database + c = DriverManager.getConnection(database, user, password); + + // Tell it we will not do any updates. + // This hint may improve efficiency. + c.setReadOnly(true); + + // If the "-l" option was given, then list the members of + // the named package or class. Otherwise, lookup all + // matches for the specified member, class, or package. + if (list) list(c, target); + else lookup(c, target); + } + // If anything goes wrong, print the exception and a usage message. If + // a SQLException is thrown, display the state message it includes. + catch (Exception e) { + System.out.println(e); + if (e instanceof SQLException) + System.out.println(((SQLException) e).getSQLState()); + System.out.println("Usage: java LookupAPI [-l] [-p ] " + + "target"); + } + // Always close the DB connection when we're done with it. + finally { + try { c.close(); } catch (Exception e) {} + } + } + + /** + * This method looks up all matches for the specified target string in the + * database. First, it prints the full name of any members by that name. + * Then it prints the full name of any classes by that name. Then it + * prints the name of any packages that contain the specified name + **/ + public static void lookup(Connection c, String target) throws SQLException + { + // Create the Statement object we'll use to query the database + Statement s = c.createStatement(); + + // Go find all class members with the specified name + s.executeQuery("SELECT DISTINCT " + + "package.name, class.name, member.name, member.isField"+ + " FROM package, class, member" + + " WHERE member.name='" + target + "'" + + " AND member.classId=class.id " + + " AND class.packageId=package.id"); + + // Loop through the results, and print them out (if there are any). + ResultSet r = s.getResultSet(); + while(r.next()) { + String pkg = r.getString(1); // package name + String cls = r.getString(2); // class name + String member = r.getString(3); // member name + boolean isField = r.getBoolean(4); // is the member a field? + // Display this match + System.out.println(pkg + "." + cls + "." + member + + (isField?"":"()")); + } + + // Now look for a class with the specified name + s.executeQuery("SELECT package.name, class.name " + + "FROM package, class " + + "WHERE class.name='" + target + "' " + + " AND class.packageId=package.id"); + // Loop through the results and print them out + r = s.getResultSet(); + while(r.next()) System.out.println(r.getString(1) + "." + + r.getString(2)); + + // Finally, look for a package that matches a part of of the name. + // Note the use of the SQL LIKE keyword and % wildcard characters + s.executeQuery("SELECT name FROM package " + + "WHERE name='" + target + "'" + + " OR name LIKE '%." + target + ".%' " + + " OR name LIKE '" + target + ".%' " + + " OR name LIKE '%." + target + "'"); + // Loop through the results and print them out + r = s.getResultSet(); + while(r.next()) System.out.println(r.getString(1)); + + // Finally, close the Statement object + s.close(); + } + + /** + * This method looks for classes with the specified name, or packages + * that contain the specified name. For each class it finds, it displays + * all methods and fields of the class. For each package it finds, it + * displays all classes in the package. + **/ + public static void list(Connection conn, String target) throws SQLException + { + // Create two Statement objects to query the database with + Statement s = conn.createStatement(); + Statement t = conn.createStatement(); + + // Look for a class with the given name + s.executeQuery("SELECT package.name, class.name " + + "FROM package, class " + + "WHERE class.name='" + target + "' " + + " AND class.packageId=package.id"); + // Loop through all matches + ResultSet r = s.getResultSet(); + while(r.next()) { + String p = r.getString(1); // package name + String c = r.getString(2); // class name + // Print out the matching class name + System.out.println("class " + p + "." + c + " {"); + + // Now query all members of the class + t.executeQuery("SELECT DISTINCT member.name, member.isField " + + "FROM package, class, member " + + "WHERE package.name = '" + p + "' " + + " AND class.name = '" + c + "' " + + " AND member.classId=class.id " + + " AND class.packageId=package.id " + + "ORDER BY member.isField, member.name"); + + // Loop through the ordered list of all members, and print them out + ResultSet r2 = t.getResultSet(); + while(r2.next()) { + String m = r2.getString(1); + int isField = r2.getInt(2); + System.out.println(" " + m + ((isField == 1)?"":"()")); + } + // End the class listing + System.out.println("}"); + } + + // Now go look for a package that matches the specified name + s.executeQuery("SELECT name FROM package " + + "WHERE name='" + target + "'" + + " OR name LIKE '%." + target + ".%' " + + " OR name LIKE '" + target + ".%' " + + " OR name LIKE '%." + target + "'"); + // Loop through any matching packages + r = s.getResultSet(); + while(r.next()) { + // Display the name of the package + String p = r.getString(1); + System.out.println("Package " + p + ": "); + + // Get a list of all classes and interfaces in the package + t.executeQuery("SELECT class.name FROM package, class " + + "WHERE package.name='" + p + "' " + + " AND class.packageId=package.id " + + "ORDER BY class.name"); + // Loop through the list and print them out. + ResultSet r2 = t.getResultSet(); + while(r2.next()) System.out.println(" " + r2.getString(1)); + } + + // Finally, close both Statement objects + s.close(); t.close(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/sql/MakeAPIDB.java b/src/main/java/com/davidflanagan/examples/sql/MakeAPIDB.java new file mode 100644 index 0000000..3a6c988 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/sql/MakeAPIDB.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.sql; +import java.sql.*; +import java.lang.reflect.*; +import java.io.*; +import java.util.*; + +/** + * This class is a standalone program that reads a list of classes and + * builds a database of packages, classes, and class fields and methods. + **/ +public class MakeAPIDB { + public static void main(String args[]) { + Connection c = null; // The connection to the database + try { + // Read the classes to index from a file specified by args[0] + ArrayList classnames = new ArrayList(); + BufferedReader in = new BufferedReader(new FileReader(args[0])); + String name; + while((name = in.readLine()) != null) classnames.add(name); + + // Now determine the values needed to set up the database + // connection The program attempts to read a property file named + // "APIDB.props", or optionally specified by args[1]. This + // property file (if any) may contain "driver", "database", "user", + // and "password" properties that specify the necessary values for + // connecting to the db. If the properties file does not exist, or + // does not contain the named properties, defaults will be used. + Properties p = new Properties(); // Empty properties + try { + p.load(new FileInputStream(args[1])); // Try to load properties + } + catch (Exception e1) { + try { p.load(new FileInputStream("APIDB.props")); } + catch (Exception e2) {} + } + + // Read values from Properties file + String driver = p.getProperty("driver"); + String database = p.getProperty("database"); + String user = p.getProperty("user", ""); + String password = p.getProperty("password", ""); + + // The driver and database properties are mandatory + if (driver == null) + throw new IllegalArgumentException("No driver specified!"); + if (database == null) + throw new IllegalArgumentException("No database specified!"); + + // Load the driver. It registers itself with DriverManager. + Class.forName(driver); + + // And set up a connection to the specified database + c = DriverManager.getConnection(database, user, password); + + // Create three new tables for our data + // The package table contains a package id and a package name. + // The class table contains a class id, a package id, and a name. + // The member table contains a class id, a member name, and an bit + // that indicates whether the class member is a field or a method. + Statement s = c.createStatement(); + s.executeUpdate("CREATE TABLE package " + + "(id INT, name VARCHAR(80))"); + s.executeUpdate("CREATE TABLE class " + + "(id INT, packageId INT, name VARCHAR(48))"); + s.executeUpdate("CREATE TABLE member " + + "(classId INT, name VARCHAR(48), isField BIT)"); + + // Prepare some statements that will be used to insert records into + // these three tables. + insertpackage = + c.prepareStatement("INSERT INTO package VALUES(?,?)"); + insertclass = + c.prepareStatement("INSERT INTO class VALUES(?,?,?)"); + insertmember = + c.prepareStatement("INSERT INTO member VALUES(?,?,?)"); + + // Now loop through the list of classes and use reflection + // to store them all in the tables + int numclasses = classnames.size(); + for(int i = 0; i < numclasses; i++) { + try { + storeClass((String)classnames.get(i)); + } + catch(ClassNotFoundException e) { + System.out.println("WARNING: class not found: " + + classnames.get(i) + "; SKIPPING"); + } + } + } + catch (Exception e) { + System.err.println(e); + if (e instanceof SQLException) + System.err.println("SQLState: " + + ((SQLException)e).getSQLState()); + System.err.println("Usage: java MakeAPIDB " + + " "); + } + // When we're done, close the connection to the database + finally { try { c.close(); } catch (Exception e) {} } + } + + /** + * This hash table records the mapping between package names and package + * id. This is the only one we need to store temporarily. The others are + * stored in the db and don't have to be looked up by this program + **/ + static Map package_to_id = new HashMap(); + + // Counters for the package and class identifier columns + static int packageId = 0, classId = 0; + + // Some prepared SQL statements for use in inserting + // new values into the tables. Initialized in main() above. + static PreparedStatement insertpackage, insertclass, insertmember; + + /** + * Given a fully-qualified classname, this method stores the package name + * in the package table (if it is not already there), stores the class name + * in the class table, and then uses the Java Reflection API to look up all + * methods and fields of the class, and stores those in the member table. + **/ + public static void storeClass(String name) + throws SQLException, ClassNotFoundException + { + String packagename, classname; + + // Dynamically load the class. + Class c = Class.forName(name); + + // Display output so the user knows that the program is progressing + System.out.println("Storing data for: " + name); + + // Figure out the packagename and the classname + int pos = name.lastIndexOf('.'); + if (pos == -1) { + packagename = ""; + classname = name; + } + else { + packagename = name.substring(0,pos); + classname = name.substring(pos+1); + } + + // Figure out what the package id is. If there is one, then this + // package has already been stored in the database. Otherwise, assign + // an id, and store it and the packagename in the db. + Integer pid; + pid = (Integer)package_to_id.get(packagename); // Check hashtable + if (pid == null) { + pid = new Integer(++packageId); // Assign an id + package_to_id.put(packagename, pid); // Remember it + insertpackage.setInt(1, packageId); // Set statement args + insertpackage.setString(2, packagename); + insertpackage.executeUpdate(); // Insert into package db + } + + // Now, store the classname in the class table of the database. + // This record includes the package id, so that the class is linked to + // the package that contains it. To store the class, we set arguments + // to the PreparedStatement, then execute that statement + insertclass.setInt(1, ++classId); // Set class identifier + insertclass.setInt(2, pid.intValue()); // Set package identifier + insertclass.setString(3, classname); // Set class name + insertclass.executeUpdate(); // Insert the class record + + // Now, get a list of all non-private methods of the class, and + // insert those into the "members" table of the database. Each + // record includes the class id of the containing class, and also + // a value that indicates that these are methods, not fields. + Method[] methods = c.getDeclaredMethods(); // Get a list of methods + for(int i = 0; i < methods.length; i++) { // For all non-private + if (Modifier.isPrivate(methods[i].getModifiers())) continue; + insertmember.setInt(1, classId); // Set the class id + insertmember.setString(2, methods[i].getName()); // Set method name + insertmember.setBoolean(3, false); // It is not a field + insertmember.executeUpdate(); // Insert into db + } + + // Do the same thing for the non-private fields of the class + Field[] fields = c.getDeclaredFields(); // Get a list of fields + for(int i = 0; i < fields.length; i++) { // For each non-private + if (Modifier.isPrivate(fields[i].getModifiers())) continue; + insertmember.setInt(1, classId); // Set the class id + insertmember.setString(2, fields[i].getName()); // Set field name + insertmember.setBoolean(3, true); // It is a field + insertmember.executeUpdate(); // Insert the record + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/sql/RemoteDBBankServer.java b/src/main/java/com/davidflanagan/examples/sql/RemoteDBBankServer.java new file mode 100644 index 0000000..0bd23f8 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/sql/RemoteDBBankServer.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.sql; +import java.rmi.*; +import java.rmi.server.*; +import java.rmi.registry.*; +import java.sql.*; +import java.io.*; +import java.util.*; +import java.util.Date; // import explicitly to disambiguate from java.sql.Date +import com.davidflanagan.examples.rmi.Bank.*; // Import inner classes of Bank + +/** + * This class is another implementation of the RemoteBank interface. + * It uses a database connection as its back end, so that client data isn't + * lost if the server goes down. Note that it takes the database connection + * out of "auto commit" mode and explicitly calls commit() and rollback() to + * ensure that updates happen atomically. + **/ +public class RemoteDBBankServer extends UnicastRemoteObject + implements RemoteBank +{ + Connection db; // The connection to the database that stores account info + + /** The constructor. Just save the database connection object away */ + public RemoteDBBankServer(Connection db) throws RemoteException { + this.db = db; + } + + /** Open an account */ + public synchronized void openAccount(String name, String password) + throws RemoteException, BankingException + { + // First, check if there is already an account with that name + Statement s = null; + try { + s = db.createStatement(); + s.executeQuery("SELECT * FROM accounts WHERE name='" + name + "'"); + ResultSet r = s.getResultSet(); + if (r.next()) throw new BankingException("Account name in use."); + + // If it doesn't exist, go ahead and create it Also, create a + // table for the transaction history of this account and insert an + // initial transaction into it. + s = db.createStatement(); + s.executeUpdate("INSERT INTO accounts VALUES ('" + name + "', '" + + password + "', 0)"); + s.executeUpdate("CREATE TABLE " + name + + "_history (msg VARCHAR(80))"); + s.executeUpdate("INSERT INTO " + name + "_history " + + "VALUES ('Account opened at " + new Date() + "')"); + + // And if we've been successful so far, commit these updates, + // ending the atomic transaction. All the methods below also use + // this atomic transaction commit/rollback scheme + db.commit(); + } + catch(SQLException e) { + // If an exception was thrown, "rollback" the prior updates, + // removing them from the database. This also ends the atomic + // transaction. + try { db.rollback(); } catch (Exception e2) {} + // Pass the SQLException on in the body of a BankingException + throw new BankingException("SQLException: " + e.getMessage() + + ": " + e.getSQLState()); + } + // No matter what happens, don't forget to close the DB Statement + finally { try { s.close(); } catch (Exception e) {} } + } + + /** + * This convenience method checks whether the name and password match + * an existing account. If so, it returns the balance in that account. + * If not, it throws an exception. Note that this method does not call + * commit() or rollback(), so its query is part of a larger transaction. + **/ + public int verify(String name, String password) + throws BankingException, SQLException + { + Statement s = null; + try { + s = db.createStatement(); + s.executeQuery("SELECT balance FROM accounts " + + "WHERE name='" + name + "' " + + " AND password = '" + password + "'"); + ResultSet r = s.getResultSet(); + if (!r.next()) + throw new BankingException("Bad account name or password"); + return r.getInt(1); + } + finally { try { s.close(); } catch (Exception e) {} } + } + + /** Close a named account */ + public synchronized FunnyMoney closeAccount(String name, String password) + throws RemoteException, BankingException + { + int balance = 0; + Statement s = null; + try { + balance = verify(name, password); + s = db.createStatement(); + // Delete the account from the accounts table + s.executeUpdate("DELETE FROM accounts " + + "WHERE name = '" + name + "' " + + " AND password = '" + password + "'"); + // And drop the transaction history table for this account + s.executeUpdate("DROP TABLE " + name + "_history"); + db.commit(); + } + catch (SQLException e) { + try { db.rollback(); } catch (Exception e2) {} + throw new BankingException("SQLException: " + e.getMessage() + + ": " + e.getSQLState()); + } + finally { try { s.close(); } catch (Exception e) {} } + + // Finally, return whatever balance remained in the account + return new FunnyMoney(balance); + } + + /** Deposit the specified money into the named account */ + public synchronized void deposit(String name, String password, + FunnyMoney money) + throws RemoteException, BankingException + { + int balance = 0; + Statement s = null; + try { + balance = verify(name, password); + s = db.createStatement(); + // Update the balance + s.executeUpdate("UPDATE accounts " + + "SET balance = " + balance + money.amount + " " + + "WHERE name='" + name + "' " + + " AND password = '" + password + "'"); + // Add a row to the transaction history + s.executeUpdate("INSERT INTO " + name + "_history " + + "VALUES ('Deposited " + money.amount + + " at " + new Date() + "')"); + db.commit(); + } + catch (SQLException e) { + try { db.rollback(); } catch (Exception e2) {} + throw new BankingException("SQLException: " + e.getMessage() + + ": " + e.getSQLState()); + } + finally { try { s.close(); } catch (Exception e) {} } + } + + /** Withdraw the specified amount from the named account */ + public synchronized FunnyMoney withdraw(String name, String password, + int amount) + throws RemoteException, BankingException + { + int balance = 0; + Statement s = null; + try { + balance = verify(name, password); + if (balance < amount) + throw new BankingException("Insufficient Funds"); + s = db.createStatement(); + // Update the account balance + s.executeUpdate("UPDATE accounts " + + "SET balance = " + (balance - amount) + " " + + "WHERE name='" + name + "' " + + " AND password = '" + password + "'"); + // Add a row to the transaction history + s.executeUpdate("INSERT INTO " + name + "_history " + + "VALUES ('Withdrew " + amount + + " at " + new Date() + "')"); + db.commit(); + } + catch (SQLException e) { + try { db.rollback(); } catch (Exception e2) {} + throw new BankingException("SQLException: " + e.getMessage() + + ": " + e.getSQLState()); + } + finally { try { s.close(); } catch (Exception e) {} } + + return new FunnyMoney(amount); + } + + /** Return the balance of the specified account */ + public synchronized int getBalance(String name, String password) + throws RemoteException, BankingException + { + int balance; + try { + // Get the balance + balance = verify(name, password); + // Commit the transaction + db.commit(); + } + catch (SQLException e) { + try { db.rollback(); } catch (Exception e2) {} + throw new BankingException("SQLException: " + e.getMessage() + + ": " + e.getSQLState()); + } + // Return the balance + return balance; + } + + /** Get the transaction history of the named account */ + public synchronized List getTransactionHistory(String name, + String password) + throws RemoteException, BankingException + { + Statement s = null; + List list = new ArrayList(); + try { + // Call verify to check the password, even though we don't + // care what the current balance is. + verify(name, password); + s = db.createStatement(); + // Request everything out of the history table + s.executeQuery("SELECT * from " + name + "_history"); + // Get the results of the query and put them in a Vector + ResultSet r = s.getResultSet(); + while(r.next()) list.add(r.getString(1)); + // Commit the transaction + db.commit(); + } + catch (SQLException e) { + try { db.rollback(); } catch (Exception e2) {} + throw new BankingException("SQLException: " + e.getMessage() + + ": " + e.getSQLState()); + } + finally { try { s.close(); } catch (Exception e) {} } + // Return the Vector of transaction history. + return list; + } + + /** + * This main() method is the standalone program that figures out what + * database to connect to with what driver, connects to the database, + * creates a RemoteDBBankServer object, and registers it with the registry, + * making it available for client use + **/ + public static void main(String[] args) { + try { + // Create a new Properties object. Attempt to initialize it from + // the BankDB.props file or the file optionally specified on the + // command line, ignoring errors. + Properties p = new Properties(); + try { p.load(new FileInputStream(args[0])); } + catch (Exception e) { + try { p.load(new FileInputStream("BankDB.props")); } + catch (Exception e2) {} + } + + // The BankDB.props file (or file specified on the command line) + // must contain properties "driver" and "database", and may + // optionally contain properties "user" and "password". + String driver = p.getProperty("driver"); + String database = p.getProperty("database"); + String user = p.getProperty("user", ""); + String password = p.getProperty("password", ""); + + // Load the database driver class + Class.forName(driver); + + // Connect to the database that stores our accounts + Connection db = DriverManager.getConnection(database, + user, password); + + // Configure the database to allow multiple queries and updates + // to be grouped into atomic transactions + db.setAutoCommit(false); + db.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + + // Create a server object that uses our database connection + RemoteDBBankServer bank = new RemoteDBBankServer(db); + + // Read a system property to figure out how to name this server. + // Use "SecondRemote" as the default. + String name = System.getProperty("bankname", "SecondRemote"); + + // Register the server with the name + Naming.rebind(name, bank); + + // And tell everyone that we're up and running. + System.out.println(name + " is open and ready for customers."); + } + catch (Exception e) { + System.err.println(e); + if (e instanceof SQLException) + System.err.println("SQL State: " + + ((SQLException)e).getSQLState()); + System.err.println("Usage: java [-Dbankname=] " + + "com.davidflanagan.examples.sql.RemoteDBBankServer " + + "[]"); + System.exit(1); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/sql/classlist b/src/main/java/com/davidflanagan/examples/sql/classlist new file mode 100644 index 0000000..e55a32e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/sql/classlist @@ -0,0 +1,849 @@ +java.applet.Applet +java.applet.AppletContext +java.applet.AppletStub +java.applet.AudioClip +java.awt.AWTError +java.awt.AWTEvent +java.awt.AWTEventMulticaster +java.awt.AWTException +java.awt.AWTPermission +java.awt.ActiveEvent +java.awt.Adjustable +java.awt.AlphaComposite +java.awt.BasicStroke +java.awt.BorderLayout +java.awt.Button +java.awt.Canvas +java.awt.CardLayout +java.awt.Checkbox +java.awt.CheckboxGroup +java.awt.CheckboxMenuItem +java.awt.Choice +java.awt.Color +java.awt.Component +java.awt.ComponentOrientation +java.awt.Composite +java.awt.CompositeContext +java.awt.Container +java.awt.Cursor +java.awt.Dialog +java.awt.Dimension +java.awt.Event +java.awt.EventQueue +java.awt.FileDialog +java.awt.FlowLayout +java.awt.Font +java.awt.FontFormatException +java.awt.FontMetrics +java.awt.Frame +java.awt.GradientPaint +java.awt.Graphics +java.awt.Graphics2D +java.awt.GraphicsConfigTemplate +java.awt.GraphicsConfiguration +java.awt.GraphicsDevice +java.awt.GraphicsEnvironment +java.awt.GridBagConstraints +java.awt.GridBagLayout +java.awt.GridLayout +java.awt.IllegalComponentStateException +java.awt.Image +java.awt.Insets +java.awt.ItemSelectable +java.awt.JobAttributes +java.awt.JobAttributes$DefaultSelectionType +java.awt.JobAttributes$DestinationType +java.awt.JobAttributes$DialogType +java.awt.JobAttributes$MultipleDocumentHandlingType +java.awt.JobAttributes$SidesType +java.awt.Label +java.awt.LayoutManager +java.awt.LayoutManager2 +java.awt.List +java.awt.MediaTracker +java.awt.Menu +java.awt.MenuBar +java.awt.MenuComponent +java.awt.MenuContainer +java.awt.MenuItem +java.awt.MenuShortcut +java.awt.PageAttributes +java.awt.PageAttributes$ColorType +java.awt.PageAttributes$MediaType +java.awt.PageAttributes$OrientationRequestedType +java.awt.PageAttributes$OriginType +java.awt.PageAttributes$PrintQualityType +java.awt.Paint +java.awt.PaintContext +java.awt.Panel +java.awt.Point +java.awt.Polygon +java.awt.PopupMenu +java.awt.PrintGraphics +java.awt.PrintJob +java.awt.Rectangle +java.awt.RenderingHints +java.awt.RenderingHints$Key +java.awt.Robot +java.awt.ScrollPane +java.awt.Scrollbar +java.awt.Shape +java.awt.Stroke +java.awt.SystemColor +java.awt.TextArea +java.awt.TextComponent +java.awt.TextField +java.awt.TextSelectionEventMulticaster +java.awt.TexturePaint +java.awt.Toolkit +java.awt.Transparency +java.awt.Window +java.awt.color.CMMException +java.awt.color.ColorSpace +java.awt.color.ICC_ColorSpace +java.awt.color.ICC_Profile +java.awt.color.ICC_ProfileGray +java.awt.color.ICC_ProfileRGB +java.awt.color.ProfileDataException +java.awt.datatransfer.Clipboard +java.awt.datatransfer.ClipboardOwner +java.awt.datatransfer.DataFlavor +java.awt.datatransfer.FlavorMap +java.awt.datatransfer.MimeTypeParseException +java.awt.datatransfer.StringSelection +java.awt.datatransfer.SystemFlavorMap +java.awt.datatransfer.Transferable +java.awt.datatransfer.UnsupportedFlavorException +java.awt.dnd.Autoscroll +java.awt.dnd.DnDConstants +java.awt.dnd.DragGestureEvent +java.awt.dnd.DragGestureListener +java.awt.dnd.DragGestureRecognizer +java.awt.dnd.DragSource +java.awt.dnd.DragSourceContext +java.awt.dnd.DragSourceDragEvent +java.awt.dnd.DragSourceDropEvent +java.awt.dnd.DragSourceEvent +java.awt.dnd.DragSourceListener +java.awt.dnd.DropTarget +java.awt.dnd.DropTarget$DropTargetAutoScroller +java.awt.dnd.DropTargetContext +java.awt.dnd.DropTargetContext$TransferableProxy +java.awt.dnd.DropTargetDragEvent +java.awt.dnd.DropTargetDropEvent +java.awt.dnd.DropTargetEvent +java.awt.dnd.DropTargetListener +java.awt.dnd.InvalidDnDOperationException +java.awt.dnd.MouseDragGestureRecognizer +java.awt.event.AWTEventListener +java.awt.event.ActionEvent +java.awt.event.ActionListener +java.awt.event.AdjustmentEvent +java.awt.event.AdjustmentListener +java.awt.event.ComponentAdapter +java.awt.event.ComponentEvent +java.awt.event.ComponentListener +java.awt.event.ContainerAdapter +java.awt.event.ContainerEvent +java.awt.event.ContainerListener +java.awt.event.FocusAdapter +java.awt.event.FocusEvent +java.awt.event.FocusListener +java.awt.event.HierarchyBoundsAdapter +java.awt.event.HierarchyBoundsListener +java.awt.event.HierarchyEvent +java.awt.event.HierarchyListener +java.awt.event.InputEvent +java.awt.event.InputMethodEvent +java.awt.event.InputMethodListener +java.awt.event.InvocationEvent +java.awt.event.ItemEvent +java.awt.event.ItemListener +java.awt.event.KeyAdapter +java.awt.event.KeyEvent +java.awt.event.KeyListener +java.awt.event.MouseAdapter +java.awt.event.MouseEvent +java.awt.event.MouseListener +java.awt.event.MouseMotionAdapter +java.awt.event.MouseMotionListener +java.awt.event.PaintEvent +java.awt.event.TextEvent +java.awt.event.TextListener +java.awt.event.TextSelectionEvent +java.awt.event.TextSelectionListener +java.awt.event.WindowAdapter +java.awt.event.WindowEvent +java.awt.event.WindowListener +java.awt.font.FontRenderContext +java.awt.font.GlyphJustificationInfo +java.awt.font.GlyphMetrics +java.awt.font.GlyphVector +java.awt.font.GraphicAttribute +java.awt.font.ImageGraphicAttribute +java.awt.font.LineBreakMeasurer +java.awt.font.LineMetrics +java.awt.font.MultipleMaster +java.awt.font.OpenType +java.awt.font.ShapeGraphicAttribute +java.awt.font.TextAttribute +java.awt.font.TextHitInfo +java.awt.font.TextLayout +java.awt.font.TextLayout$CaretPolicy +java.awt.font.TextMeasurer +java.awt.font.TransformAttribute +java.awt.geom.AffineTransform +java.awt.geom.Arc2D +java.awt.geom.Arc2D$Double +java.awt.geom.Arc2D$Float +java.awt.geom.Area +java.awt.geom.CubicCurve2D +java.awt.geom.CubicCurve2D$Double +java.awt.geom.CubicCurve2D$Float +java.awt.geom.Dimension2D +java.awt.geom.Ellipse2D +java.awt.geom.Ellipse2D$Double +java.awt.geom.Ellipse2D$Float +java.awt.geom.FlatteningPathIterator +java.awt.geom.GeneralPath +java.awt.geom.IllegalPathStateException +java.awt.geom.Line2D +java.awt.geom.Line2D$Double +java.awt.geom.Line2D$Float +java.awt.geom.NoninvertibleTransformException +java.awt.geom.PathIterator +java.awt.geom.Point2D +java.awt.geom.Point2D$Double +java.awt.geom.Point2D$Float +java.awt.geom.QuadCurve2D +java.awt.geom.QuadCurve2D$Double +java.awt.geom.QuadCurve2D$Float +java.awt.geom.Rectangle2D +java.awt.geom.Rectangle2D$Double +java.awt.geom.Rectangle2D$Float +java.awt.geom.RectangularShape +java.awt.geom.RoundRectangle2D +java.awt.geom.RoundRectangle2D$Double +java.awt.geom.RoundRectangle2D$Float +java.awt.im.InputContext +java.awt.im.InputMethodHighlight +java.awt.im.InputMethodRequests +java.awt.im.InputSubset +java.awt.image.AffineTransformOp +java.awt.image.AreaAveragingScaleFilter +java.awt.image.BandCombineOp +java.awt.image.BandedSampleModel +java.awt.image.BufferedImage +java.awt.image.BufferedImageFilter +java.awt.image.BufferedImageOp +java.awt.image.ByteLookupTable +java.awt.image.ColorConvertOp +java.awt.image.ColorModel +java.awt.image.ComponentColorModel +java.awt.image.ComponentSampleModel +java.awt.image.ConvolveOp +java.awt.image.CropImageFilter +java.awt.image.DataBuffer +java.awt.image.DataBufferByte +java.awt.image.DataBufferInt +java.awt.image.DataBufferShort +java.awt.image.DataBufferUShort +java.awt.image.DirectColorModel +java.awt.image.FilteredImageSource +java.awt.image.ImageConsumer +java.awt.image.ImageFilter +java.awt.image.ImageObserver +java.awt.image.ImageProducer +java.awt.image.ImagingOpException +java.awt.image.IndexColorModel +java.awt.image.Kernel +java.awt.image.LookupOp +java.awt.image.LookupTable +java.awt.image.MemoryImageSource +java.awt.image.MultiPixelPackedSampleModel +java.awt.image.PackedColorModel +java.awt.image.PixelGrabber +java.awt.image.PixelInterleavedSampleModel +java.awt.image.RGBImageFilter +java.awt.image.Raster +java.awt.image.RasterFormatException +java.awt.image.RasterOp +java.awt.image.RenderedImage +java.awt.image.ReplicateScaleFilter +java.awt.image.RescaleOp +java.awt.image.SampleModel +java.awt.image.ShortLookupTable +java.awt.image.SinglePixelPackedSampleModel +java.awt.image.TileObserver +java.awt.image.WritableRaster +java.awt.image.WritableRenderedImage +java.awt.print.Book +java.awt.print.PageFormat +java.awt.print.Pageable +java.awt.print.Paper +java.awt.print.Printable +java.awt.print.PrinterAbortException +java.awt.print.PrinterException +java.awt.print.PrinterGraphics +java.awt.print.PrinterIOException +java.awt.print.PrinterJob +java.beans.AppletInitializer +java.beans.BeanDescriptor +java.beans.BeanInfo +java.beans.Beans +java.beans.Customizer +java.beans.DesignMode +java.beans.EventSetDescriptor +java.beans.FeatureDescriptor +java.beans.IndexedPropertyDescriptor +java.beans.IntrospectionException +java.beans.Introspector +java.beans.MethodDescriptor +java.beans.ParameterDescriptor +java.beans.PropertyChangeEvent +java.beans.PropertyChangeListener +java.beans.PropertyChangeSupport +java.beans.PropertyDescriptor +java.beans.PropertyEditor +java.beans.PropertyEditorManager +java.beans.PropertyEditorSupport +java.beans.PropertyVetoException +java.beans.SimpleBeanInfo +java.beans.VetoableChangeListener +java.beans.VetoableChangeSupport +java.beans.Visibility +java.beans.beancontext.BeanContext +java.beans.beancontext.BeanContextChild +java.beans.beancontext.BeanContextChildComponentProxy +java.beans.beancontext.BeanContextChildSupport +java.beans.beancontext.BeanContextContainerProxy +java.beans.beancontext.BeanContextEvent +java.beans.beancontext.BeanContextMembershipEvent +java.beans.beancontext.BeanContextMembershipListener +java.beans.beancontext.BeanContextProxy +java.beans.beancontext.BeanContextServiceAvailableEvent +java.beans.beancontext.BeanContextServiceProvider +java.beans.beancontext.BeanContextServiceProviderBeanInfo +java.beans.beancontext.BeanContextServiceRevokedEvent +java.beans.beancontext.BeanContextServiceRevokedListener +java.beans.beancontext.BeanContextServices +java.beans.beancontext.BeanContextServicesListener +java.beans.beancontext.BeanContextServicesSupport +java.beans.beancontext.BeanContextServicesSupport$BCSSChild +java.beans.beancontext.BeanContextServicesSupport$BCSSProxyServiceProvider +java.beans.beancontext.BeanContextServicesSupport$BCSSServiceProvider +java.beans.beancontext.BeanContextSupport +java.beans.beancontext.BeanContextSupport$BCSChild +java.beans.beancontext.BeanContextSupport$BCSIterator +java.io.BufferedInputStream +java.io.BufferedOutputStream +java.io.BufferedReader +java.io.BufferedWriter +java.io.ByteArrayInputStream +java.io.ByteArrayOutputStream +java.io.CharArrayReader +java.io.CharArrayWriter +java.io.CharConversionException +java.io.DataInput +java.io.DataInputStream +java.io.DataOutput +java.io.DataOutputStream +java.io.EOFException +java.io.Externalizable +java.io.File +java.io.FileDescriptor +java.io.FileFilter +java.io.FileInputStream +java.io.FileNotFoundException +java.io.FileOutputStream +java.io.FilePermission +java.io.FileReader +java.io.FileWriter +java.io.FilenameFilter +java.io.FilterInputStream +java.io.FilterOutputStream +java.io.FilterReader +java.io.FilterWriter +java.io.IOException +java.io.InputStream +java.io.InputStreamReader +java.io.InterruptedIOException +java.io.InvalidClassException +java.io.InvalidObjectException +java.io.LineNumberInputStream +java.io.LineNumberReader +java.io.NotActiveException +java.io.NotSerializableException +java.io.ObjectInput +java.io.ObjectInputStream +java.io.ObjectInputStream$GetField +java.io.ObjectInputValidation +java.io.ObjectOutput +java.io.ObjectOutputStream +java.io.ObjectOutputStream$PutField +java.io.ObjectStreamClass +java.io.ObjectStreamConstants +java.io.ObjectStreamException +java.io.ObjectStreamField +java.io.OptionalDataException +java.io.OutputStream +java.io.OutputStreamWriter +java.io.PipedInputStream +java.io.PipedOutputStream +java.io.PipedReader +java.io.PipedWriter +java.io.PrintStream +java.io.PrintWriter +java.io.PushbackInputStream +java.io.PushbackReader +java.io.RandomAccessFile +java.io.Reader +java.io.SequenceInputStream +java.io.Serializable +java.io.SerializablePermission +java.io.StreamCorruptedException +java.io.StreamTokenizer +java.io.StringBufferInputStream +java.io.StringReader +java.io.StringWriter +java.io.SyncFailedException +java.io.UTFDataFormatException +java.io.UnsupportedEncodingException +java.io.WriteAbortedException +java.io.Writer +java.lang.AbstractMethodError +java.lang.ArithmeticException +java.lang.ArrayIndexOutOfBoundsException +java.lang.ArrayStoreException +java.lang.Boolean +java.lang.Byte +java.lang.Character +java.lang.Character$Subset +java.lang.Character$UnicodeBlock +java.lang.Class +java.lang.ClassCastException +java.lang.ClassCircularityError +java.lang.ClassFormatError +java.lang.ClassLoader +java.lang.ClassNotFoundException +java.lang.CloneNotSupportedException +java.lang.Cloneable +java.lang.Comparable +java.lang.Compiler +java.lang.Double +java.lang.Error +java.lang.Exception +java.lang.ExceptionInInitializerError +java.lang.Float +java.lang.IllegalAccessError +java.lang.IllegalAccessException +java.lang.IllegalArgumentException +java.lang.IllegalMonitorStateException +java.lang.IllegalStateException +java.lang.IllegalThreadStateException +java.lang.IncompatibleClassChangeError +java.lang.IndexOutOfBoundsException +java.lang.InheritableThreadLocal +java.lang.InstantiationError +java.lang.InstantiationException +java.lang.Integer +java.lang.InternalError +java.lang.InterruptedException +java.lang.LinkageError +java.lang.Long +java.lang.Math +java.lang.NegativeArraySizeException +java.lang.NoClassDefFoundError +java.lang.NoSuchFieldError +java.lang.NoSuchFieldException +java.lang.NoSuchMethodError +java.lang.NoSuchMethodException +java.lang.NullPointerException +java.lang.Number +java.lang.NumberFormatException +java.lang.Object +java.lang.OutOfMemoryError +java.lang.Package +java.lang.Process +java.lang.Runnable +java.lang.Runtime +java.lang.RuntimeException +java.lang.RuntimePermission +java.lang.SecurityException +java.lang.SecurityManager +java.lang.Short +java.lang.StackOverflowError +java.lang.StrictMath +java.lang.String +java.lang.StringBuffer +java.lang.StringIndexOutOfBoundsException +java.lang.System +java.lang.Thread +java.lang.ThreadDeath +java.lang.ThreadGroup +java.lang.ThreadLocal +java.lang.Throwable +java.lang.UnknownError +java.lang.UnsatisfiedLinkError +java.lang.UnsupportedClassVersionError +java.lang.UnsupportedOperationException +java.lang.VerifyError +java.lang.VirtualMachineError +java.lang.Void +java.lang.ref.PhantomReference +java.lang.ref.Reference +java.lang.ref.ReferenceQueue +java.lang.ref.SoftReference +java.lang.ref.WeakReference +java.lang.reflect.Array +java.lang.reflect.Constructor +java.lang.reflect.Field +java.lang.reflect.InvocationHandler +java.lang.reflect.InvocationTargetException +java.lang.reflect.Member +java.lang.reflect.Method +java.lang.reflect.Modifier +java.lang.reflect.Proxy +java.lang.reflect.ReflectPermission +java.lang.reflect.UndeclaredThrowableException +java.math.BigDecimal +java.math.BigInteger +java.net.Authenticator +java.net.BindException +java.net.ConnectException +java.net.ContentHandler +java.net.ContentHandlerFactory +java.net.DatagramPacket +java.net.DatagramSocket +java.net.DatagramSocketImpl +java.net.DatagramSocketImplFactory +java.net.FileNameMap +java.net.HttpURLConnection +java.net.InetAddress +java.net.JarURLConnection +java.net.MalformedURLException +java.net.MulticastSocket +java.net.NetPermission +java.net.NoRouteToHostException +java.net.PasswordAuthentication +java.net.ProtocolException +java.net.ServerSocket +java.net.Socket +java.net.SocketException +java.net.SocketImpl +java.net.SocketImplFactory +java.net.SocketOptions +java.net.SocketPermission +java.net.URL +java.net.URLClassLoader +java.net.URLConnection +java.net.URLDecoder +java.net.URLEncoder +java.net.URLStreamHandler +java.net.URLStreamHandlerFactory +java.net.UnknownHostException +java.net.UnknownServiceException +java.rmi.AccessException +java.rmi.AlreadyBoundException +java.rmi.ConnectException +java.rmi.ConnectIOException +java.rmi.MarshalException +java.rmi.MarshalledObject +java.rmi.Naming +java.rmi.NoSuchObjectException +java.rmi.NotBoundException +java.rmi.RMISecurityException +java.rmi.RMISecurityManager +java.rmi.Remote +java.rmi.RemoteException +java.rmi.ServerError +java.rmi.ServerException +java.rmi.ServerRuntimeException +java.rmi.StubNotFoundException +java.rmi.UnexpectedException +java.rmi.UnknownHostException +java.rmi.UnmarshalException +java.rmi.activation.Activatable +java.rmi.activation.ActivateFailedException +java.rmi.activation.ActivationDesc +java.rmi.activation.ActivationException +java.rmi.activation.ActivationGroup +java.rmi.activation.ActivationGroupDesc +java.rmi.activation.ActivationGroupDesc$CommandEnvironment +java.rmi.activation.ActivationGroupID +java.rmi.activation.ActivationID +java.rmi.activation.ActivationInstantiator +java.rmi.activation.ActivationMonitor +java.rmi.activation.ActivationSystem +java.rmi.activation.Activator +java.rmi.activation.UnknownGroupException +java.rmi.activation.UnknownObjectException +java.rmi.dgc.DGC +java.rmi.dgc.Lease +java.rmi.dgc.VMID +java.rmi.registry.LocateRegistry +java.rmi.registry.Registry +java.rmi.registry.RegistryHandler +java.rmi.server.ExportException +java.rmi.server.LoaderHandler +java.rmi.server.LogStream +java.rmi.server.ObjID +java.rmi.server.Operation +java.rmi.server.RMIClassLoader +java.rmi.server.RMIClientSocketFactory +java.rmi.server.RMIFailureHandler +java.rmi.server.RMIServerSocketFactory +java.rmi.server.RMISocketFactory +java.rmi.server.RemoteCall +java.rmi.server.RemoteObject +java.rmi.server.RemoteRef +java.rmi.server.RemoteServer +java.rmi.server.RemoteStub +java.rmi.server.ServerCloneException +java.rmi.server.ServerNotActiveException +java.rmi.server.ServerRef +java.rmi.server.Skeleton +java.rmi.server.SkeletonMismatchException +java.rmi.server.SkeletonNotFoundException +java.rmi.server.SocketSecurityException +java.rmi.server.UID +java.rmi.server.UnicastRemoteObject +java.rmi.server.Unreferenced +java.security.AccessControlContext +java.security.AccessControlException +java.security.AccessController +java.security.AlgorithmParameterGenerator +java.security.AlgorithmParameterGeneratorSpi +java.security.AlgorithmParameters +java.security.AlgorithmParametersSpi +java.security.AllPermission +java.security.BasicPermission +java.security.Certificate +java.security.CodeSource +java.security.DigestException +java.security.DigestInputStream +java.security.DigestOutputStream +java.security.DomainCombiner +java.security.GeneralSecurityException +java.security.Guard +java.security.GuardedObject +java.security.Identity +java.security.IdentityScope +java.security.InvalidAlgorithmParameterException +java.security.InvalidKeyException +java.security.InvalidParameterException +java.security.Key +java.security.KeyException +java.security.KeyFactory +java.security.KeyFactorySpi +java.security.KeyManagementException +java.security.KeyPair +java.security.KeyPairGenerator +java.security.KeyPairGeneratorSpi +java.security.KeyStore +java.security.KeyStoreException +java.security.KeyStoreSpi +java.security.MessageDigest +java.security.MessageDigestSpi +java.security.NoSuchAlgorithmException +java.security.NoSuchProviderException +java.security.Permission +java.security.PermissionCollection +java.security.Permissions +java.security.Policy +java.security.Principal +java.security.PrivateKey +java.security.PrivilegedAction +java.security.PrivilegedActionException +java.security.PrivilegedExceptionAction +java.security.ProtectionDomain +java.security.Provider +java.security.ProviderException +java.security.PublicKey +java.security.SecureClassLoader +java.security.SecureRandom +java.security.SecureRandomSpi +java.security.Security +java.security.SecurityPermission +java.security.Signature +java.security.SignatureException +java.security.SignatureSpi +java.security.SignedObject +java.security.Signer +java.security.UnrecoverableKeyException +java.security.UnresolvedPermission +java.security.acl.Acl +java.security.acl.AclEntry +java.security.acl.AclNotFoundException +java.security.acl.Group +java.security.acl.LastOwnerException +java.security.acl.NotOwnerException +java.security.acl.Owner +java.security.acl.Permission +java.security.cert.CRL +java.security.cert.CRLException +java.security.cert.Certificate +java.security.cert.Certificate.CertificateRep +java.security.cert.CertificateEncodingException +java.security.cert.CertificateException +java.security.cert.CertificateExpiredException +java.security.cert.CertificateFactory +java.security.cert.CertificateFactorySpi +java.security.cert.CertificateNotYetValidException +java.security.cert.CertificateParsingException +java.security.cert.X509CRL +java.security.cert.X509CRLEntry +java.security.cert.X509Certificate +java.security.cert.X509Extension +java.security.interfaces.DSAKey +java.security.interfaces.DSAKeyPairGenerator +java.security.interfaces.DSAParams +java.security.interfaces.DSAPrivateKey +java.security.interfaces.DSAPublicKey +java.security.interfaces.RSAKey +java.security.interfaces.RSAPrivateCrtKey +java.security.interfaces.RSAPrivateKey +java.security.interfaces.RSAPublicKey +java.security.spec.AlgorithmParameterSpec +java.security.spec.DSAParameterSpec +java.security.spec.DSAPrivateKeySpec +java.security.spec.DSAPublicKeySpec +java.security.spec.EncodedKeySpec +java.security.spec.InvalidKeySpecException +java.security.spec.InvalidParameterSpecException +java.security.spec.KeySpec +java.security.spec.PKCS8EncodedKeySpec +java.security.spec.RSAKeyGenParameterSpec +java.security.spec.RSAPrivateCrtKeySpec +java.security.spec.RSAPrivateKeySpec +java.security.spec.RSAPublicKeySpec +java.security.spec.X509EncodedKeySpec +java.sql.Array +java.sql.BatchUpdateException +java.sql.Blob +java.sql.CallableStatement +java.sql.Clob +java.sql.Connection +java.sql.DataTruncation +java.sql.DatabaseMetaData +java.sql.Date +java.sql.Driver +java.sql.DriverManager +java.sql.DriverPropertyInfo +java.sql.PreparedStatement +java.sql.Ref +java.sql.ResultSet +java.sql.ResultSetMetaData +java.sql.SQLData +java.sql.SQLException +java.sql.SQLInput +java.sql.SQLOutput +java.sql.SQLPermission +java.sql.SQLWarning +java.sql.Statement +java.sql.Struct +java.sql.Time +java.sql.Timestamp +java.sql.Types +java.text.Annotation +java.text.AttributedCharacterIterator +java.text.AttributedCharacterIterator$Attribute +java.text.AttributedString +java.text.BreakIterator +java.text.CharacterIterator +java.text.ChoiceFormat +java.text.CollationElementIterator +java.text.CollationKey +java.text.Collator +java.text.DateFormat +java.text.DateFormatSymbols +java.text.DecimalFormat +java.text.DecimalFormatSymbols +java.text.FieldPosition +java.text.Format +java.text.MessageFormat +java.text.NumberFormat +java.text.ParseException +java.text.ParsePosition +java.text.RuleBasedCollator +java.text.SimpleDateFormat +java.text.StringCharacterIterator +java.util.AbstractCollection +java.util.AbstractList +java.util.AbstractMap +java.util.AbstractSequentialList +java.util.AbstractSet +java.util.ArrayList +java.util.Arrays +java.util.BitSet +java.util.Calendar +java.util.Collection +java.util.Collections +java.util.Comparator +java.util.ConcurrentModificationException +java.util.Date +java.util.Dictionary +java.util.EmptyStackException +java.util.Enumeration +java.util.EventListener +java.util.EventObject +java.util.GregorianCalendar +java.util.HashMap +java.util.HashSet +java.util.Hashtable +java.util.Iterator +java.util.LinkedList +java.util.List +java.util.ListIterator +java.util.ListResourceBundle +java.util.Locale +java.util.Map +java.util.Map$Entry +java.util.MissingResourceException +java.util.NoSuchElementException +java.util.Observable +java.util.Observer +java.util.Properties +java.util.PropertyPermission +java.util.PropertyResourceBundle +java.util.Random +java.util.ResourceBundle +java.util.Set +java.util.SimpleTimeZone +java.util.SortedMap +java.util.SortedSet +java.util.Stack +java.util.StringTokenizer +java.util.TimeZone +java.util.Timer +java.util.TimerTask +java.util.TooManyListenersException +java.util.TreeMap +java.util.TreeSet +java.util.Vector +java.util.WeakHashMap +java.util.jar.Attributes +java.util.jar.Attributes$Name +java.util.jar.JarEntry +java.util.jar.JarException +java.util.jar.JarFile +java.util.jar.JarInputStream +java.util.jar.JarOutputStream +java.util.jar.Manifest +java.util.zip.Adler32 +java.util.zip.CRC32 +java.util.zip.CheckedInputStream +java.util.zip.CheckedOutputStream +java.util.zip.Checksum +java.util.zip.DataFormatException +java.util.zip.Deflater +java.util.zip.DeflaterOutputStream +java.util.zip.GZIPInputStream +java.util.zip.GZIPOutputStream +java.util.zip.Inflater +java.util.zip.InflaterInputStream +java.util.zip.ZipEntry +java.util.zip.ZipException +java.util.zip.ZipFile +java.util.zip.ZipInputStream +java.util.zip.ZipOutputStream diff --git a/src/main/java/com/davidflanagan/examples/thread/Deadlock.java b/src/main/java/com/davidflanagan/examples/thread/Deadlock.java new file mode 100644 index 0000000..c440c82 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/thread/Deadlock.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.thread; + +/** + * This is a demonstration of how NOT to write multi-threaded programs. + * It is a program that purposely causes deadlock between two threads that + * are both trying to acquire locks for the same two resources. + * To avoid this sort of deadlock when locking multiple resources, all threads + * should always acquire their locks in the same order. + **/ +public class Deadlock { + public static void main(String[] args) { + // These are the two resource objects we'll try to get locks for + final Object resource1 = "resource1"; + final Object resource2 = "resource2"; + // Here's the first thread. It tries to lock resource1 then resource2 + Thread t1 = new Thread() { + public void run() { + // Lock resource 1 + synchronized(resource1) { + System.out.println("Thread 1: locked resource 1"); + + // Pause for a bit, simulating some file I/O or + // something. Basically, we just want to give the + // other thread a chance to run. Threads and deadlock + // are asynchronous things, but we're trying to force + // deadlock to happen here... + try { Thread.sleep(50); } + catch (InterruptedException e) {} + + // Now wait 'till we can get a lock on resource 2 + synchronized(resource2) { + System.out.println("Thread 1: locked resource 2"); + } + } + } + }; + + // Here's the second thread. It tries to lock resource2 then resource1 + Thread t2 = new Thread() { + public void run() { + // This thread locks resource 2 right away + synchronized(resource2) { + System.out.println("Thread 2: locked resource 2"); + + // Then it pauses, just like the first thread. + try { Thread.sleep(50); } + catch (InterruptedException e) {} + + // Then it tries to lock resource1. But wait! Thread + // 1 locked resource1, and won't release it 'till it + // gets a lock on resource2. This thread holds the + // lock on resource2, and won't release it 'till it + // gets resource1. We're at an impasse. Neither + // thread can run, and the program freezes up. + synchronized(resource1) { + System.out.println("Thread 2: locked resource 1"); + } + } + } + }; + + // Start the two threads. If all goes as planned, deadlock will occur, + // and the program will never exit. + t1.start(); + t2.start(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/thread/ThreadDemo.java b/src/main/java/com/davidflanagan/examples/thread/ThreadDemo.java new file mode 100644 index 0000000..7d01eec --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/thread/ThreadDemo.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.thread; + +/** + * This class demonstrates the use of threads. The main() method is the + * initial method invoked by the interpreter. It defines and starts two + * more threads and the three threads run at the same time. Note that this + * class extends Thread and overrides its run() method. That method provides + * the body of one of the threads started by the main() method + **/ +public class ThreadDemo extends Thread { + /** + * This method overrides the run() method of Thread. It provides + * the body for this thread. + **/ + public void run() { for(int i = 0; i < 5; i++) compute(); } + + /** + * This main method creates and starts two threads in addition to the + * initial thread that the interpreter creates to invoke the main() method. + **/ + public static void main(String[] args) { + // Create the first thread: an instance of this class. Its body is + // the run() method above + ThreadDemo thread1 = new ThreadDemo(); + + // Create the second thread by passing a Runnable object to the + // Thread() construtor. The body of this thread is the run() method + // of the anonymous Runnable object below. + Thread thread2 = new Thread(new Runnable() { + public void run() { for(int i = 0; i < 5; i++) compute(); } + }); + + // Set the priorities of these two threads, if any are specified + if (args.length >= 1) thread1.setPriority(Integer.parseInt(args[0])); + if (args.length >= 2) thread2.setPriority(Integer.parseInt(args[1])); + + // Start the two threads running + thread1.start(); + thread2.start(); + + // This main() method is run by the initial thread created by the + // Java interpreter. Now that thread does some stuff, too. + for(int i = 0; i < 5; i++) compute(); + + // We could wait for the threads to stop running with these lines + // But they aren't necessary here, so we don't bother. + // try { + // thread1.join(); + // thread2.join(); + // } catch (InterruptedException e) {} + + // The Java VM exits only when the main() method returns, and when all + // threads stop running (except for daemon threads--see setDaemon()). + } + + // ThreadLocal objects respresent a value accessed with get() and set(). + // But they maintain a different value for each thread. This object keeps + // track of how many times each thread has called compute(). + static ThreadLocal numcalls = new ThreadLocal(); + + /** This is the dummy method our threads all call */ + static synchronized void compute() { + // Figure out how many times we've been called by the current thread + Integer n = (Integer) numcalls.get(); + if (n == null) n = new Integer(1); + else n = new Integer(n.intValue() + 1); + numcalls.set(n); + + // Display the name of the thread, and the number of times called + System.out.println(Thread.currentThread().getName() + ": " + n); + + // Do a long computation, simulating a "compute-bound" thread + for(int i = 0, j=0; i < 1000000; i++) j += i; + + // Alternatively, we can simulate a thread subject to network or I/O + // delays by causing it to sleep for a random amount of time: + try { + // Stop running for a random number of milliseconds + Thread.sleep((int)(Math.random()*100+1)); + } + catch (InterruptedException e) {} + + // Each thread politely offers the other threads a chance to run. + // This is important so that a compute-bound thread does not "starve" + // other threads of equal priority. + Thread.yield(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/thread/ThreadLister.java b/src/main/java/com/davidflanagan/examples/thread/ThreadLister.java new file mode 100644 index 0000000..6a982eb --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/thread/ThreadLister.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.thread; +import java.io.*; +import java.awt.*; // AWT classes for the demo program +import javax.swing.*; // Swing GUI classes for the demo + +/** + * This class contains a useful static method for listing all threads + * and threadgroups in the VM. It also has a simple main() method so it + * can be run as a standalone program. + **/ +public class ThreadLister { + /** Display information about a thread. */ + private static void printThreadInfo(PrintWriter out, Thread t, + String indent) { + if (t == null) return; + out.println(indent + "Thread: " + t.getName() + + " Priority: " + t.getPriority() + + (t.isDaemon()?" Daemon":"") + + (t.isAlive()?"":" Not Alive")); + } + + /** Display info about a thread group and its threads and groups */ + private static void printGroupInfo(PrintWriter out, ThreadGroup g, + String indent) { + if (g == null) return; + int num_threads = g.activeCount(); + int num_groups = g.activeGroupCount(); + Thread[] threads = new Thread[num_threads]; + ThreadGroup[] groups = new ThreadGroup[num_groups]; + + g.enumerate(threads, false); + g.enumerate(groups, false); + + out.println(indent + "Thread Group: " + g.getName() + + " Max Priority: " + g.getMaxPriority() + + (g.isDaemon()?" Daemon":"")); + + for(int i = 0; i < num_threads; i++) + printThreadInfo(out, threads[i], indent + " "); + for(int i = 0; i < num_groups; i++) + printGroupInfo(out, groups[i], indent + " "); + } + + /** Find the root thread group and list it recursively */ + public static void listAllThreads(PrintWriter out) { + ThreadGroup current_thread_group; + ThreadGroup root_thread_group; + ThreadGroup parent; + + // Get the current thread group + current_thread_group = Thread.currentThread().getThreadGroup(); + + // Now go find the root thread group + root_thread_group = current_thread_group; + parent = root_thread_group.getParent(); + while(parent != null) { + root_thread_group = parent; + parent = parent.getParent(); + } + + // And list it, recursively + printGroupInfo(out, root_thread_group, ""); + } + + /** + * The main() method create a simple graphical user interface to display + * the threads in. This allows us to see the "event dispatch thread" used + * by AWT and Swing. + **/ + public static void main(String[] args) { + // Create a simple Swing GUI + JFrame frame = new JFrame("ThreadLister Demo"); + JTextArea textarea = new JTextArea(); + frame.getContentPane().add(new JScrollPane(textarea), + BorderLayout.CENTER); + frame.setSize(500, 400); + frame.setVisible(true); + + // Get the threadlisting as a string using a StringWriter stream + StringWriter sout = new StringWriter(); // To capture the listing + PrintWriter out = new PrintWriter(sout); + ThreadLister.listAllThreads(out); // List threads to stream + out.close(); + String threadListing = sout.toString(); // Get listing as a string + + // Finally, display the thread listing in the GUI + textarea.setText(threadListing); + } +} diff --git a/src/main/java/com/davidflanagan/examples/thread/Timer.java b/src/main/java/com/davidflanagan/examples/thread/Timer.java new file mode 100644 index 0000000..9cf6b96 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/thread/Timer.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.thread; +import java.util.Date; +import java.util.TreeSet; +import java.util.Comparator; + +/** + * This class is a simple implementation of the Java 1.3 java.util.Timer API + **/ +public class Timer { + // This sorted set stores the tasks that this Timer is responsible for. + // It uses a comparator (defined below) to sort the task by execution time. + TreeSet tasks = new TreeSet(new TimerTaskComparator()); + + // This is the thread the timer uses to execute the tasks + TimerThread timer; + + /** This constructor create a Timer that does not use a daemon thread */ + public Timer() { this(false); } + + /** The main constructor: the internal thread is a daemon if specified */ + public Timer(boolean isDaemon) { + timer = new TimerThread(isDaemon); // TimerThread is defined below + timer.start(); // Start the thread running + } + + /** Stop the timer thread, and discard all scheduled tasks */ + public void cancel() { + synchronized(tasks) { // Only one thread at a time! + timer.pleaseStop(); // Set a flag asking the thread to stop + tasks.clear(); // Discard all tasks + tasks.notify(); // Wake up the thread if it is in wait(). + } + } + + /** Schedule a single execution after delay milliseconds */ + public void schedule(TimerTask task, long delay) { + task.schedule(System.currentTimeMillis() + delay, 0, false); + schedule(task); + } + + /** Schedule a single execution at the specified time */ + public void schedule(TimerTask task, Date time) { + task.schedule(time.getTime(), 0, false); + schedule(task); + } + + /** Schedule a periodic execution starting at the specified time */ + public void schedule(TimerTask task, Date firstTime, long period) { + task.schedule(firstTime.getTime(), period, false); + schedule(task); + } + + /** Schedule a periodic execution starting after the specified delay */ + public void schedule(TimerTask task, long delay, long period) { + task.schedule(System.currentTimeMillis() + delay, period, false); + schedule(task); + } + + /** + * Schedule a periodic execution starting after the specified delay. + * Schedule fixed-rate executions period ms after the start of the last. + * Instead of fixed-interval executions measured from the end of the last. + **/ + public void scheduleAtFixedRate(TimerTask task, long delay, long period) { + task.schedule(System.currentTimeMillis() + delay, period, true); + schedule(task); + } + + /** Schedule a periodic execution starting after the specified time */ + public void scheduleAtFixedRate(TimerTask task, Date firstTime, + long period) + { + task.schedule(firstTime.getTime(), period, true); + schedule(task); + } + + + // This internal method adds a task to the sorted set of tasks + void schedule(TimerTask task) { + synchronized(tasks) { // Only one thread can modify tasks at a time! + tasks.add(task); // Add the task to the sorted set of tasks + tasks.notify(); // Wake up the thread if it is waiting + } + } + + /** + * This inner class is used to sort tasks by next execution time. + **/ + static class TimerTaskComparator implements Comparator { + public int compare(Object a, Object b) { + TimerTask t1 = (TimerTask) a; + TimerTask t2 = (TimerTask) b; + long diff = t1.nextTime - t2.nextTime; + if (diff < 0) return -1; + else if (diff > 0) return 1; + else return 0; + } + public boolean equals(Object o) { return this == o; } + } + + /** + * This inner class defines the thread that runs each of the tasks at their + * scheduled times + **/ + class TimerThread extends Thread { + // This flag is will be set true to tell the thread to stop running. + // Note that it is declared volatile, which means that it may be + // changed asynchronously by another thread, so threads must always + // read its true value, and not used a cached version. + volatile boolean stopped = false; + + // The constructor + public TimerThread(boolean isDaemon) { setDaemon(isDaemon); } + + // Ask the thread to stop by setting the flag above + public void pleaseStop() { stopped = true; } + + // This is the body of the thread + public void run() { + TimerTask readyToRun = null; // Is there a task to run right now? + + // The thread loops until the stopped flag is set to true. + while(!stopped) { + // If there is a task that is ready to run, then run it! + if (readyToRun != null) { + if (readyToRun.cancelled) { // If it was cancelled, skip. + readyToRun = null; + continue; + } + // Run the task. + readyToRun.run(); + // Ask it to reschedule itself, and if it wants to run + // again, then insert it back into the set of tasks. + if (readyToRun.reschedule()) + schedule(readyToRun); + // We've run it, so there is nothing to run now + readyToRun = null; + // Go back to top of the loop to see if we've been stopped + continue; + } + + // Now acquire a lock on the set of tasks + synchronized(tasks) { + long timeout; // how many ms 'till the next execution? + + if (tasks.isEmpty()) { // If there aren't any tasks + timeout = 0; // Wait 'till notified of a new task + } + else { + // If there are scheduled tasks, then get the first one + // Since the set is sorted, this is the next one. + TimerTask t = (TimerTask) tasks.first(); + // How long 'till it is next run? + timeout = t.nextTime - System.currentTimeMillis(); + // Check whether it needs to run now + if (timeout <= 0) { + readyToRun = t; // Save it as ready to run + tasks.remove(t); // Remove it from the set + // Break out of the synchronized section before + // we run the task + continue; + } + } + + // If we get here, there is nothing ready to run now, + // so wait for time to run out, or wait 'till notify() is + // called when something new is added to the set of tasks. + try { tasks.wait(timeout); } + catch (InterruptedException e) {} + + // When we wake up, go back up to the top of the while loop + } + } + } + } + + /** This inner class defines a test program */ + public static class Test { + public static void main(String[] args) { + final TimerTask t1 = new TimerTask() { // Task 1: print "boom" + public void run() { System.out.println("boom"); } + }; + final TimerTask t2 = new TimerTask() { // Task 2: print "BOOM" + public void run() { System.out.println("\tBOOM"); } + }; + final TimerTask t3 = new TimerTask() { // Task 3: cancel the tasks + public void run() { t1.cancel(); t2.cancel(); } + }; + + // Create a timer, and schedule some tasks + final Timer timer = new Timer(); + timer.schedule(t1, 0, 500); // boom every .5sec starting now + timer.schedule(t2, 2000, 2000); // BOOM every 2s, starting in 2s + timer.schedule(t3, 5000); // Stop them after 5 seconds + + // Schedule a final task: starting in 5 seconds, count + // down from 5, then destroy the timer, which, since it is + // the only remaining thread, will cause the program to exit. + timer.scheduleAtFixedRate(new TimerTask() { + public int times = 5; + public void run() { + System.out.println(times--); + if (times == 0) timer.cancel(); + } + }, + 5000,500); + } + } +} diff --git a/src/main/java/com/davidflanagan/examples/thread/TimerTask.java b/src/main/java/com/davidflanagan/examples/thread/TimerTask.java new file mode 100644 index 0000000..d30ad68 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/thread/TimerTask.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.thread; + +/** + * This class implements the same API as the Java 1.3 java.util.TimerTask. + * Note that a TimerTask can only be scheduled on one Timer at a time, but + * that this implementation does not enforce that constraint. + **/ +public abstract class TimerTask implements Runnable { + boolean cancelled = false; // Has it been cancelled? + long nextTime = -1; // When is it next scheduled? + long period; // What is the execution interval + boolean fixedRate; // Fixed-rate execution? + + protected TimerTask() {} + + /** + * Cancel the execution of the task. Return true if it was actually + * running, or false if it was already cancelled or never scheduled. + **/ + public boolean cancel() { + if (cancelled) return false; // Already cancelled; + cancelled = true; // Cancel it + if (nextTime == -1) return false; // Never scheduled; + return true; + } + + /** + * When it the timer scheduled to execute? The run() method can use this + * to see whether it was invoked when it was supposed to be + **/ + public long scheduledExecutionTime() { return nextTime; } + + /** + * Subclasses must override this to provide that code that is to be run. + * The Timer class will invoke this from its internal thread. + **/ + public abstract void run(); + + // This method is used by Timer to tell the Task how it is scheduled. + void schedule(long nextTime, long period, boolean fixedRate) { + this.nextTime = nextTime; + this.period = period; + this.fixedRate = fixedRate; + } + + // This will be called by Timer after Timer calls the run method. + boolean reschedule() { + if (period == 0 || cancelled) return false; // Don't run it again + if (fixedRate) nextTime += period; + else nextTime = System.currentTimeMillis() + period; + return true; + } +} diff --git a/src/main/java/com/davidflanagan/examples/xml/DOMTreeWalkerTreeModel.java b/src/main/java/com/davidflanagan/examples/xml/DOMTreeWalkerTreeModel.java new file mode 100644 index 0000000..636921b --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/xml/DOMTreeWalkerTreeModel.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.xml; +import org.w3c.dom.*; // Core DOM classes +import org.w3c.dom.traversal.*; // TreeWalker and related DOM classes +import org.apache.xerces.parsers.*; // Apache Xerces parser classes +import org.xml.sax.*; // Xerces DOM parser uses some SAX classes +import javax.swing.*; // Swing classes +import javax.swing.tree.*; // TreeModel and related classes +import javax.swing.event.*; // Tree-related event classes +import java.io.*; // For reading the input XML file + +/** + * This class implements the Swing TreeModel interface so that the DOM tree + * returned by a TreeWalker can be displayed in a JTree component. + **/ +public class DOMTreeWalkerTreeModel implements TreeModel { + TreeWalker walker; // The TreeWalker we're modeling for JTree + + /** Create a TreeModel for the specified TreeWalker */ + public DOMTreeWalkerTreeModel(TreeWalker walker) { this.walker = walker; } + + /** + * Create a TreeModel for a TreeWalker that returns all nodes + * in the specified document + **/ + public DOMTreeWalkerTreeModel(Document document) { + DocumentTraversal dt = (DocumentTraversal)document; + walker = dt.createTreeWalker(document, NodeFilter.SHOW_ALL,null,false); + } + + /** + * Create a TreeModel for a TreeWalker that returns the specified + * element and all of its descendant nodes. + **/ + public DOMTreeWalkerTreeModel(Element element) { + DocumentTraversal dt = (DocumentTraversal)element.getOwnerDocument(); + walker = dt.createTreeWalker(element, NodeFilter.SHOW_ALL, null,false); + } + + // Return the root of the tree + public Object getRoot() { return walker.getRoot(); } + + // Is this node a leaf? (Leaf nodes are displayed differently by JTree) + public boolean isLeaf(Object node) { + walker.setCurrentNode((Node)node); // Set current node + Node child = walker.firstChild(); // Ask for a child + return (child == null); // Does it have any? + } + + // How many children does this node have? + public int getChildCount(Object node) { + walker.setCurrentNode((Node)node); // Set the current node + // TreeWalker doesn't count children for us, so we count ourselves + int numkids = 0; + Node child = walker.firstChild(); // Start with the first child + while(child != null) { // Loop 'till there are no more + numkids++; // Update the count + child = walker.nextSibling(); // Get next child + } + return numkids; // This is the number of children + } + + // Return the specified child of a parent node. + public Object getChild(Object parent, int index) { + walker.setCurrentNode((Node)parent); // Set the current node + // TreeWalker provides sequential access to children, not random + // access, so we've got to loop through the kids one by one + Node child = walker.firstChild(); + while(index-- > 0) child = walker.nextSibling(); + return child; + } + + // Return the index of the child node in the parent node + public int getIndexOfChild(Object parent, Object child) { + walker.setCurrentNode((Node)parent); // Set current node + int index = 0; + Node c = walker.firstChild(); // Start with first child + while((c != child) && (c != null)) { // Loop 'till we find a match + index++; + c = walker.nextSibling(); // Get the next child + } + return index; // Return matching position + } + + // Only required for editable trees; unimplemented here. + public void valueForPathChanged(TreePath path, Object newvalue) {} + + // This TreeModel never fires any events (since it is not editable) + // so event listener registration methods are left unimplemented + public void addTreeModelListener(TreeModelListener l) {} + public void removeTreeModelListener(TreeModelListener l) {} + + /** + * This main() method demonstrates the use of this class, the use of the + * Xerces DOM parser, and the creation of a DOM Level 2 TreeWalker object. + **/ + public static void main(String[] args) throws IOException, SAXException { + // Obtain an instance of a Xerces parser to build a DOM tree. + // Note that we are not using the JAXP API here, so this + // code uses Apache Xerces APIs that are not standards + DOMParser parser = new org.apache.xerces.parsers.DOMParser(); + + // Get a java.io.Reader for the input XML file and + // wrap the input file in a SAX input source + Reader in = new BufferedReader(new FileReader(args[0])); + InputSource input = new InputSource(in); + + // Tell the Xerces parser to parse the input source + parser.parse(input); + + // Ask the parser to give us our DOM Document. Once we've got the DOM + // tree, we don't have to use the Apache Xerces APIs any more; from + // here on, we use the standard DOM APIs + Document document = parser.getDocument(); + + // If we're using a DOM Level 2 implementation, then our Document + // object ought to implement DocumentTraversal + DocumentTraversal traversal = (DocumentTraversal)document; + + // For this demonstration, we create a NodeFilter that filters out + // Text nodes containing only space; these just clutter up the tree + NodeFilter filter = new NodeFilter() { + public short acceptNode(Node n) { + if (n.getNodeType() == Node.TEXT_NODE) { + // Use trim() to strip off leading and trailing space. + // If nothing is left, then reject the node + if (((Text)n).getData().trim().length() == 0) + return NodeFilter.FILTER_REJECT; + } + return NodeFilter.FILTER_ACCEPT; + } + }; + + // This set of flags says to "show" all node types except comments + int whatToShow = NodeFilter.SHOW_ALL & ~NodeFilter.SHOW_COMMENT; + + // Create a TreeWalker using the filter and the flags + TreeWalker walker = traversal.createTreeWalker(document, whatToShow, + filter, false); + + // Instantiate a TreeModel and a JTree to display it + JTree tree = new JTree(new DOMTreeWalkerTreeModel(walker)); + + // Create a frame and a scrollpane to display the tree, and pop them up + JFrame frame = new JFrame("DOMTreeWalkerTreeModel Demo"); + frame.getContentPane().add(new JScrollPane(tree)); + frame.setSize(500, 250); + frame.setVisible(true); + } +} diff --git a/src/main/java/com/davidflanagan/examples/xml/ListServlets1.java b/src/main/java/com/davidflanagan/examples/xml/ListServlets1.java new file mode 100644 index 0000000..e9d3130 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/xml/ListServlets1.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.xml; +import javax.xml.parsers.*; // The JAXP package +import org.xml.sax.*; // The main SAX package +import java.io.*; + +/** + * Parse a web.xml file using JAXP and SAX1. Print out the names + * and class names of all servlets listed in the file. + * + * This class implements the HandlerBase helper class, which means + * that it defines all the "callback" methods that the SAX parser will + * invoke to notify the application. In this example we override the + * methods that we require. + * + * This example uses full package names in places to help keep the JAXP + * and SAX APIs distinct. + **/ +public class ListServlets1 extends HandlerBase { + /** The main method sets things up for parsing */ + public static void main(String[] args) + throws IOException, SAXException, ParserConfigurationException + { + // Create a JAXP "parser factory" for creating SAX parsers + SAXParserFactory spf=SAXParserFactory.newInstance(); + + // Configure the parser factory for the type of parsers we require + spf.setValidating(false); // No validation required + + // Now use the parser factory to create a SAXParser object + // Note that SAXParser is a JAXP class, not a SAX class + SAXParser sp = spf.newSAXParser(); + + // Create a SAX input source for the file argument + InputSource input=new InputSource(new FileReader(args[0])); + + // Give the InputSource an absolute URL for the file, so that + // it can resolve relative URLs in a declaration, e.g. + input.setSystemId("file://" + new File(args[0]).getAbsolutePath()); + + // Create an instance of this class; it defines all the handler methods + ListServlets1 handler = new ListServlets1(); + + // Finally, tell the parser to parse the input and notify the handler + sp.parse(input, handler); + + // Instead of using the SAXParser.parse() method, which is part of the + // JAXP API, we could also use the SAX1 API directly. Note the + // difference between the JAXP class javax.xml.parsers.SAXParser and + // the SAX1 class org.xml.sax.Parser + // + // org.xml.sax.Parser parser = sp.getParser(); // Get the SAX parser + // parser.setDocumentHandler(handler); // Set main handler + // parser.setErrorHandler(handler); // Set error handler + // parser.parse(input); // Parse! + } + + StringBuffer accumulator = new StringBuffer(); // Accumulate parsed text + String servletName; // The name of the servlet + String servletClass; // The class name of the servlet + String servletId; // Value of id attribute of tag + + // When the parser encounters plain text (not XML elements), it calls + // this method, which accumulates them in a string buffer + public void characters(char[] buffer, int start, int length) { + accumulator.append(buffer, start, length); + } + + // Every time the parser encounters the beginning of a new element, it + // calls this method, which resets the string buffer + public void startElement(String name, AttributeList attributes) { + accumulator.setLength(0); // Ready to accumulate new text + // If its a servlet tag, look for id attribute + if (name.equals("servlet")) + servletId = attributes.getValue("id"); + } + + // When the parser encounters the end of an element, it calls this method + public void endElement(String name) { + if (name.equals("servlet-name")) { + // After , we know the servlet name saved up + servletName = accumulator.toString().trim(); + } + else if (name.equals("servlet-class")) { + // After , we've got the class name accumulated + servletClass = accumulator.toString().trim(); + } + else if (name.equals("servlet")) { + // Assuming the document is valid, then when we parse , + // we know we've got a servlet name and class name to print out + System.out.println("Servlet " + servletName + + ((servletId != null)?" (id="+servletId+")":"") + + ": " + servletClass); + } + } + + /** This method is called when warnings occur */ + public void warning(SAXParseException exception) { + System.err.println("WARNING: line " + exception.getLineNumber() + ": "+ + exception.getMessage()); + } + + /** This method is called when errors occur */ + public void error(SAXParseException exception) { + System.err.println("ERROR: line " + exception.getLineNumber() + ": " + + exception.getMessage()); + } + + /** This method is called when non-recoverable errors occur. */ + public void fatalError(SAXParseException exception) throws SAXException { + System.err.println("FATAL: line " + exception.getLineNumber() + ": " + + exception.getMessage()); + throw(exception); + } +} diff --git a/src/main/java/com/davidflanagan/examples/xml/ListServlets2.java b/src/main/java/com/davidflanagan/examples/xml/ListServlets2.java new file mode 100644 index 0000000..803314a --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/xml/ListServlets2.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.xml; +import org.xml.sax.*; // The main SAX package +import org.xml.sax.helpers.*; // SAX helper classes +import java.io.*; // For reading the input file +import java.util.*; // Hashtable, lists, and so on + +/** + * Parse a web.xml file using the SAX2 API and the Xerces parser from the + * Apache project. + * + * This class extends DefaultHandler so that instances can serve as SAX2 + * event handlers, and can be notified by the parser of parsing events. + * We simply override the methods that receive events we're interested in + **/ +public class ListServlets2 extends DefaultHandler { + /** The main method sets things up for parsing */ + public static void main(String[] args) throws IOException, SAXException { + // Create the parser we'll use. The parser implementation is a + // Xerces class, but we use it only through the SAX XMLReader API + XMLReader parser=new org.apache.xerces.parsers.SAXParser(); + + // Specify that we don't want validation. This is the SAX2 + // API for requesting parser features. Note the use of a + // globally unique URL as the feature name. Non-validation is + // actually the default, so this line isn't really necessary. + parser.setFeature("http://xml.org/sax/features/validation", false); + + // Instantiate this class to provide handlers for the parser and + // tell the parser about the handlers + ListServlets2 handler = new ListServlets2(); + parser.setContentHandler(handler); + parser.setErrorHandler(handler); + + // Create an input source that describes the file to parse. + // Then tell the parser to parse input from that source + InputSource input=new InputSource(new FileReader(args[0])); + parser.parse(input); + } + + HashMap nameToClass; // Map from servlet name to servlet class name + HashMap nameToPatterns; // Map from servlet name to url patterns + + StringBuffer accumulator; // Accumulate text + String servletName, servletClass, servletPattern; // Remember text + + // Called at the beginning of parsing. We use it as an init() method + public void startDocument() { + accumulator = new StringBuffer(); + nameToClass = new HashMap(); + nameToPatterns = new HashMap(); + } + + // When the parser encounters plain text (not XML elements), it calls + // this method, which accumulates them in a string buffer. + // Note that this method may be called multiple times, even with no + // intervening elements. + public void characters(char[] buffer, int start, int length) { + accumulator.append(buffer, start, length); + } + + // At the beginning of each new element, erase any accumulated text. + public void startElement(String namespaceURL, String localName, + String qname, Attributes attributes) { + accumulator.setLength(0); + } + + // Take special action when we reach the end of selected elements. + // Although we don't use a validating parser, this method does assume + // that the web.xml file we're parsing is valid. + public void endElement(String namespaceURL, String localName, String qname) + { + if (localName.equals("servlet-name")) { // Store servlet name + servletName = accumulator.toString().trim(); + } + else if (localName.equals("servlet-class")) { // Store servlet class + servletClass = accumulator.toString().trim(); + } + else if (localName.equals("url-pattern")) { // Store servlet pattern + servletPattern = accumulator.toString().trim(); + } + else if (localName.equals("servlet")) { // Map name to class + nameToClass.put(servletName, servletClass); + } + else if (localName.equals("servlet-mapping")) {// Map name to pattern + List patterns = (List)nameToPatterns.get(servletName); + if (patterns == null) { + patterns = new ArrayList(); + nameToPatterns.put(servletName, patterns); + } + patterns.add(servletPattern); + } + } + + // Called at the end of parsing. Used here to print our results. + public void endDocument() { + List servletNames = new ArrayList(nameToClass.keySet()); + Collections.sort(servletNames); + for(Iterator iterator = servletNames.iterator(); iterator.hasNext();) { + String name = (String)iterator.next(); + String classname = (String)nameToClass.get(name); + List patterns = (List)nameToPatterns.get(name); + System.out.println("Servlet: " + name); + System.out.println("Class: " + classname); + if (patterns != null) { + System.out.println("Patterns:"); + for(Iterator i = patterns.iterator(); i.hasNext(); ) { + System.out.println("\t" + i.next()); + } + } + System.out.println(); + } + } + + // Issue a warning + public void warning(SAXParseException exception) { + System.err.println("WARNING: line " + exception.getLineNumber() + ": "+ + exception.getMessage()); + } + + // Report a parsing error + public void error(SAXParseException exception) { + System.err.println("ERROR: line " + exception.getLineNumber() + ": " + + exception.getMessage()); + } + + // Report a non-recoverable error and exit + public void fatalError(SAXParseException exception) throws SAXException { + System.err.println("FATAL: line " + exception.getLineNumber() + ": " + + exception.getMessage()); + throw(exception); + } +} diff --git a/src/main/java/com/davidflanagan/examples/xml/WebAppConfig.java b/src/main/java/com/davidflanagan/examples/xml/WebAppConfig.java new file mode 100644 index 0000000..4ccdcbb --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/xml/WebAppConfig.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.xml; +import javax.xml.parsers.*; // JAXP classes for parsing +import org.w3c.dom.*; // W3C DOM classes for traversing the document +import org.xml.sax.*; // SAX classes used for error handling by JAXP +import java.io.*; // For reading the input file + +/** + * A WebAppConfig object is a wrapper around a DOM tree for a web.xml + * file. The methods of the class use the DOM API to work with the + * tree in various ways. + **/ +public class WebAppConfig { + /** The main method creates and demonstrates a WebAppConfig object */ + public static void main(String[] args) + throws IOException, SAXException, ParserConfigurationException + { + // Create a new WebAppConfig object that represents the web.xml + // file specified by the first command-line argument + WebAppConfig config = new WebAppConfig(new File(args[0])); + // Query the tree for the class name associated with the specified + // servlet name + System.out.println("Class for servlet " + args[1] + " is " + + config.getServletClass(args[1])); + // Add a new servlet name-to-class mapping to the DOM tree + config.addServlet("foo", "bar"); + // And write out an XML version of the DOM tree to standard out + config.output(new PrintWriter(new OutputStreamWriter(System.out))); + } + + Document document; // This field holds the parsed DOM tree + + /** + * This constructor method is passed an XML file. It uses the JAXP API to + * obtain a DOM parser, and to parse the file into a DOM Document object, + * which is used by the remaining methods of the class. + **/ + public WebAppConfig(File configfile) + throws IOException, SAXException, ParserConfigurationException + { + // Get a JAXP parser factory object + DocumentBuilderFactory dbf = + DocumentBuilderFactory.newInstance(); + // Tell the factory what kind of parser we want + dbf.setValidating(false); + // Use the factory to get a JAXP parser object + DocumentBuilder parser = dbf.newDocumentBuilder(); + + // Tell the parser how to handle errors. Note that in the JAXP API, + // DOM parsers rely on the SAX API for error handling + parser.setErrorHandler(new ErrorHandler() { + public void warning(SAXParseException e) { + System.err.println("WARNING: " + e.getMessage()); + } + public void error(SAXParseException e) { + System.err.println("ERROR: " + e.getMessage()); + } + public void fatalError(SAXParseException e) + throws SAXException { + System.err.println("FATAL: " + e.getMessage()); + throw e; // re-throw the error + } + }); + + // Finally, use the JAXP parser to parse the file. This call returns + // A Document object. Now that we have this object, the rest of this + // class uses the DOM API to work with it; JAXP is no longer required. + document = parser.parse(configfile); + } + + /** + * This method looks for specific Element nodes in the DOM tree in order + * to figure out the classname associated with the specified servlet name + **/ + public String getServletClass(String servletName) { + // Find all elements and loop through them. + NodeList servletnodes = document.getElementsByTagName("servlet"); + int numservlets = servletnodes.getLength(); + for(int i = 0; i < numservlets; i++) { + Element servletTag = (Element)servletnodes.item(i); + // Get the first tag within the tag + Element nameTag = (Element) + servletTag.getElementsByTagName("servlet-name").item(0); + if (nameTag == null) continue; + + // The tag should have a single child of type + // Text. Get that child, and extract its text. Use trim() + // to strip whitespace from the beginning and end of it. + String name =((Text)nameTag.getFirstChild()).getData().trim(); + + // If this tag has the right name + if (servletName.equals(name)) { + // Get the matching tag + Element classTag = (Element) + servletTag.getElementsByTagName("servlet-class").item(0); + if (classTag != null) { + // Extract the tag's text as above, and return it + Text classTagContent = (Text)classTag.getFirstChild(); + return classTagContent.getNodeValue().trim(); + } + } + } + + // If we get here, no matching servlet name was found + return null; + } + + /** + * This method adds a new name-to-class mapping in in the form of + * a sub-tree to the document. + **/ + public void addServlet(String servletName, String className) { + // Create the tag + Element newNode = document.createElement("servlet"); + // Create the and tags + Element nameNode = document.createElement("servlet-name"); + Element classNode = document.createElement("servlet-class"); + // Add the name and classname text to those tags + nameNode.appendChild(document.createTextNode(servletName)); + classNode.appendChild(document.createTextNode(className)); + // And add those tags to the servlet tag + newNode.appendChild(nameNode); + newNode.appendChild(classNode); + + // Now that we've created the new sub-tree, figure out where to put + // it. This code looks for another servlet tag and inserts the new + // one right before it. Note that this code will fail if the document + // does not already contain at least one tag. + NodeList servletnodes = document.getElementsByTagName("servlet"); + Element firstServlet = (Element)servletnodes.item(0); + + // Insert the new node before the first servlet node + firstServlet.getParentNode().insertBefore(newNode, firstServlet); + } + + /** + * Output the DOM tree to the specified stream as an XML document. + * See the XMLDocumentWriter example for the details. + **/ + public void output(PrintWriter out) { + XMLDocumentWriter docwriter = new XMLDocumentWriter(out); + docwriter.write(document); + docwriter.close(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/xml/WebAppConfig2.java b/src/main/java/com/davidflanagan/examples/xml/WebAppConfig2.java new file mode 100644 index 0000000..7965e97 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/xml/WebAppConfig2.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.xml; +import java.io.*; +import java.util.*; +import org.jdom.*; +import org.jdom.input.SAXBuilder; +import org.jdom.output.XMLOutputter; + +/** + * This class is just like WebAppConfig, but it uses the JDOM (Beta 4) API + * instead of the DOM and JAXP APIs + **/ +public class WebAppConfig2 { + /** The main method creates and demonstrates a WebAppConfig2 object */ + public static void main(String[] args) + throws IOException, JDOMException + { + // Create a new WebAppConfig object that represents the web.xml + // file specified by the first command-line argument + WebAppConfig2 config = new WebAppConfig2(new File(args[0])); + + // Query the tree for the class name associated with the servlet + // name specified as the 2nd command-line argument + System.out.println("Class for servlet " + args[1] + " is " + + config.getServletClass(args[1])); + + // Add a new servlet name-to-class mapping to the DOM tree + config.addServlet("foo", "bar"); + + // And write out an XML version of the DOM tree to standard out + config.output(System.out); + } + + /** + * This field holds the parsed JDOM tree. Note that this is a JDOM + * Document, not a DOM Document. + **/ + protected org.jdom.Document document; + + /** + * Read the specified File and parse it to create a JDOM tree + **/ + public WebAppConfig2(File configfile) throws IOException, JDOMException { + // JDOM can build JDOM trees from a variety of input sources. One + // of those input sources is a SAX parser. + SAXBuilder builder = + new SAXBuilder("org.apache.xerces.parsers.SAXParser"); + // Parse the specified file and convert it to a JDOM document + document = builder.build(configfile); + } + + /** + * This method looks for specific Element nodes in the JDOM tree in order + * to figure out the classname associated with the specified servlet name + **/ + public String getServletClass(String servletName) throws JDOMException { + // Get the root element of the document. + Element root = document.getRootElement(); + + // Find all elements in the document, and loop through them + // to find one with the specified name. Note the use of java.util.List + // instead of org.w3c.dom.NodeList. + List servlets = root.getChildren("servlet"); + for(Iterator i = servlets.iterator(); i.hasNext(); ) { + Element servlet = (Element) i.next(); + // Get the text of the tag within the tag + String name = servlet.getChild("servlet-name").getContent(); + if (name.equals(servletName)) { + // If the names match, return the text of the + return servlet.getChild("servlet-class").getContent(); + } + } + return null; + } + + /** + * This method adds a new name-to-class mapping in in the form of + * a sub-tree to the document. + **/ + public void addServlet(String servletName, String className) + throws JDOMException + { + // Create the new Element that represents our new servlet + Element newServletName = new Element("servlet-name"); + newServletName.setContent(servletName); + Element newServletClass = new Element("servlet-class"); + newServletClass.setContent(className); + Element newServlet = new Element("servlet"); + newServlet.addChild(newServletName); + newServlet.addChild(newServletClass); + + // find the first child in the document + Element root = document.getRootElement(); + Element firstServlet = root.getChild("servlet"); + + // Now insert our new servlet tag before the one we just found. + Element parent = firstServlet.getParent(); + List children = parent.getChildren(); + children.add(children.indexOf(firstServlet), newServlet); + } + + /** + * Output the JDOM tree to the specified stream as an XML document. + **/ + public void output(OutputStream out) throws IOException { + // JDOM can output JDOM trees in a variety of ways (such as converting + // them to DOM trees or SAX event streams). Here we use an "outputter" + // that converts a JDOM tree to an XML document + XMLOutputter outputter = new XMLOutputter(" ", // indentation + true); // use newlines + outputter.output(document, out); + } +} diff --git a/src/main/java/com/davidflanagan/examples/xml/XMLDocumentWriter.java b/src/main/java/com/davidflanagan/examples/xml/XMLDocumentWriter.java new file mode 100644 index 0000000..0607f1e --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/xml/XMLDocumentWriter.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2000 David Flanagan. All rights reserved. + * This code is from the book Java Examples in a Nutshell, 2nd Edition. + * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied. + * You may study, use, and modify it for any non-commercial purpose. + * You may distribute it non-commercially as long as you retain this notice. + * For a commercial use license, or to purchase the book (recommended), + * visit http://www.davidflanagan.com/javaexamples2. + */ +package com.davidflanagan.examples.xml; +import org.w3c.dom.*; // W3C DOM classes for traversing the document +import java.io.*; + +/** + * Output a DOM Level 1 Document object to a java.io.PrintWriter as a simple + * XML document. This class does not handle every type of DOM node, and it + * doesn't deal with all the details of XML like DTDs, character encodings and + * preserved and ignored whitespace. However, it does output basic + * well-formed XML that can be parsed by a non-validating parser. + **/ +public class XMLDocumentWriter { + PrintWriter out; // the stream to send output to + + /** Initialize the output stream */ + public XMLDocumentWriter(PrintWriter out) { this.out = out; } + + /** Close the output stream. */ + public void close() { out.close(); } + + /** Output a DOM Node (such as a Document) to the output stream */ + public void write(Node node) { write(node, ""); } + + /** + * Output the specified DOM Node object, printing it using the specified + * indentation string + **/ + public void write(Node node, String indent) { + // The output depends on the type of the node + switch(node.getNodeType()) { + case Node.DOCUMENT_NODE: { // If its a Document node + Document doc = (Document)node; + out.println(indent + ""); // Output header + Node child = doc.getFirstChild(); // Get the first node + while(child != null) { // Loop 'till no more nodes + write(child, indent); // Output node + child = child.getNextSibling(); // Get next node + } + break; + } + case Node.DOCUMENT_TYPE_NODE: { // It is a tag + DocumentType doctype = (DocumentType) node; + // Note that the DOM Level 1 does not give us information about + // the the public or system ids of the doctype, so we can't output + // a complete tag here. We can do better with Level 2. + out.println(""); + break; + } + case Node.ELEMENT_NODE: { // Most nodes are Elements + Element elt = (Element) node; + out.print(indent + "<" + elt.getTagName()); // Begin start tag + NamedNodeMap attrs = elt.getAttributes(); // Get attributes + for(int i = 0; i < attrs.getLength(); i++) { // Loop through them + Node a = attrs.item(i); + out.print(" " + a.getNodeName() + "='" + // Print attr. name + fixup(a.getNodeValue()) + "'"); // Print attr. value + } + out.println(">"); // Finish start tag + + String newindent = indent + " "; // Increase indent + Node child = elt.getFirstChild(); // Get child + while(child != null) { // Loop + write(child, newindent); // Output child + child = child.getNextSibling(); // Get next child + } + + out.println(indent + ""); + break; + } + case Node.TEXT_NODE: { // Plain text node + Text textNode = (Text)node; + String text = textNode.getData().trim(); // Strip off space + if ((text != null) && text.length() > 0) // If non-empty + out.println(indent + fixup(text)); // print text + break; + } + case Node.PROCESSING_INSTRUCTION_NODE: { // Handle PI nodes + ProcessingInstruction pi = (ProcessingInstruction)node; + out.println(indent + ""); + break; + } + case Node.ENTITY_REFERENCE_NODE: { // Handle entities + out.println(indent + "&" + node.getNodeName() + ";"); + break; + } + case Node.CDATA_SECTION_NODE: { // Output CDATA sections + CDATASection cdata = (CDATASection)node; + // Careful! Don't put a CDATA section in the program itself! + out.println(indent + "<" + "![CDATA[" + cdata.getData() + + "]]" + ">"); + break; + } + case Node.COMMENT_NODE: { // Comments + Comment c = (Comment)node; + out.println(indent + ""); + break; + } + default: // Hopefully, this won't happen too much! + System.err.println("Ignoring node: " + node.getClass().getName()); + break; + } + } + + // This method replaces reserved characters with entities. + String fixup(String s) { + StringBuffer sb = new StringBuffer(); + int len = s.length(); + for(int i = 0; i < len; i++) { + char c = s.charAt(i); + switch(c) { + default: sb.append(c); break; + case '<': sb.append("<"); break; + case '>': sb.append(">"); break; + case '&': sb.append("&"); break; + case '"': sb.append("""); break; + case '\'': sb.append("'"); break; + } + } + return sb.toString(); + } +} diff --git a/src/main/java/com/davidflanagan/examples/xml/web-app_2.2.dtd b/src/main/java/com/davidflanagan/examples/xml/web-app_2.2.dtd new file mode 100644 index 0000000..5f439f4 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/xml/web-app_2.2.dtd @@ -0,0 +1,639 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/davidflanagan/examples/xml/web.xml b/src/main/java/com/davidflanagan/examples/xml/web.xml new file mode 100644 index 0000000..8f23373 --- /dev/null +++ b/src/main/java/com/davidflanagan/examples/xml/web.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + hello + com.davidflanagan.examples.servlet.Hello + + + + + + counter + com.davidflanagan.examples.servlet.Counter + + countfile + /tmp/counts.ser + + + saveInterval + 30000 + + + + + query + com.davidflanagan.examples.servlet.Query + + + driverClassName + org.gjt.mm.mysql.Driver + + + url + jdbc:mysql://dbserver.my.domain.com/dbname + + + username + david + + + password + secret + + + + + + + + + queryOtherDatabase + com.davidflanagan.examples.servlet.Query + + + + + logout + com.davidflanagan.examples.servlet.Logout + + + + + + + counter + *.count + + + + + 15 + + + + + + http://www.davidflanagan.com/tlds/decor_0_1.tld + + decor_0_1.tld + +