001: /*
002: * Copyright (c) 2007, Sun Microsystems, Inc.
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * * Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in
012: * the documentation and/or other materials provided with the distribution.
013: * * Neither the name of Sun Microsystems, Inc. nor the names of its
014: * contributors may be used to endorse or promote products derived
015: * from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
023: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: */
029:
030: /*
031: * DocumentEditorApp.java
032: */
033:
034: package documenteditor;
035:
036: import java.io.BufferedReader;
037: import java.io.BufferedWriter;
038: import java.io.File;
039: import java.io.FileReader;
040: import java.io.FileWriter;
041: import java.io.IOException;
042: import org.jdesktop.application.Application;
043: import org.jdesktop.application.SingleFrameApplication;
044: import org.jdesktop.application.Task;
045:
046: /**
047: * This is a very simple example of a SingleFrameApplication that
048: * loads and saves a single text document. Although it does not
049: * possess all of the usual trappings of a single-document app,
050: * like versioning or support for undo/redo, it does serve
051: * to highlight how to use actions, resources, and tasks.
052: * <p>
053: * This file contains the main class of the application. It extends the
054: * Swing Application Framework's {@code SingleFrameApplication} class
055: * and therefore takes care or simplifies things like
056: * loading resources and saving the session state.
057: * <p>
058: * This class calls the {@code DocumentEditorView} class, which
059: * contains the code for constructing the user interface and
060: * the application logic.
061: *
062: * <p>
063: * The application's state is defined by two read-only bound properties:
064: * <dl>
065: * <dt><strong>File {@link #getFile file}</strong><dt>
066: * <dd>The current text File being edited.</dd>
067: * <dt><strong>boolean {@link modified #isModified}</strong><dt>
068: * <dd>True if the current file needs to be saved.</dd>
069: * </dl>
070: * These properties are updated when the user interacts with the
071: * application. They can be used as binding sources, to monitor
072: * the application's state.
073: *
074: * <p>
075: * This application defines a small set of actions for opening
076: * and saving files: {@link #open open}, {@link #save save},
077: * and {@link #saveAs saveAs}. It inherits
078: * {@code cut/copy/paste/delete} ProxyActions from the
079: * {@code Application} class. The ProxyActions perform their
080: * action not on the component they're bound to (menu items and
081: * toolbar buttons), but on the component that currently
082: * has the keyboard focus. Their enabled state tracks the
083: * selection value of the component with the keyboard focus,
084: * as well as the contents of the system clipboard.
085: *
086: * <p>
087: * The action code that reads and writes files, runs asynchronously
088: * on background threads. The {@link #open open}, {@link #save save},
089: * and {@link #saveAs saveAs} actions all return a Task object which
090: * encapsulates the work that will be done on a background thread.
091: * The {@link #showAboutBox showAboutBox} and
092: * {@link #closeAboutBox closeAboutBox} actions do their work
093: * synchronously.
094: *
095: * <p>
096: * <strong>Warning:</strong> this application is intended as a simple
097: * example, not as a robust text editor. Read it, don't use it.
098: */
099: public class DocumentEditorApp extends SingleFrameApplication {
100:
101: /**
102: * At startup create and show the main frame of the application.
103: */
104: @Override
105: protected void startup() {
106: show(new DocumentEditorView(this ));
107: }
108:
109: /**
110: * This method is to initialize the specified window by injecting resources.
111: * Windows shown in our application come fully initialized from the GUI
112: * builder, so this additional configuration is not needed.
113: */
114: @Override
115: protected void configureWindow(java.awt.Window root) {
116: }
117:
118: /**
119: * A convenient static getter for the application instance.
120: * @return the instance of DocumentEditorApp
121: */
122: public static DocumentEditorApp getApplication() {
123: return Application.getInstance(DocumentEditorApp.class);
124: }
125:
126: /**
127: * Main method launching the application.
128: */
129: public static void main(String[] args) {
130: launch(DocumentEditorApp.class, args);
131: }
132:
133: /**
134: * A Task that saves a text String to a file. The file is not appended
135: * to, its contents are replaced by the String.
136: */
137: static class SaveTextFileTask extends Task<Void, Void> {
138:
139: private final File file;
140: private final String text;
141:
142: /**
143: * Construct a SaveTextFileTask.
144: *
145: * @param file The file to save to
146: * @param text The new contents of the file
147: */
148: SaveTextFileTask(Application app, File file, String text) {
149: super (app);
150: this .file = file;
151: this .text = text;
152: }
153:
154: /**
155: * Return the File that the {@link #getText text} will be
156: * written to.
157: *
158: * @return the value of the read-only file property.
159: */
160: public final File getFile() {
161: return file;
162: }
163:
164: /**
165: * Return the String that will be written to the
166: * {@link #getFile file}.
167: *
168: * @return the value of the read-only text property.
169: */
170: public final String getText() {
171: return text;
172: }
173:
174: private void renameFile(File oldFile, File newFile)
175: throws IOException {
176: if (!oldFile.renameTo(newFile)) {
177: String fmt = "file rename failed: %s => %s";
178: throw new IOException(String.format(fmt, oldFile,
179: newFile));
180: }
181: }
182:
183: /**
184: * Writes the {@code text} to the specified {@code file}. The
185: * implementation is conservative: the {@code text} is initially
186: * written to ${file}.tmp, then the original file is renamed
187: * ${file}.bak, and finally the temporary file is renamed to ${file}.
188: * The Task's {@code progress} property is updated as the text is
189: * written.
190: * <p>
191: * If this Task is cancelled before writing the temporary file
192: * has been completed, ${file.tmp} is deleted.
193: * <p>
194: * The conservative algorithm for saving to a file was lifted from
195: * the FileSaver class described by Ian Darwin here:
196: * <a href="http://javacook.darwinsys.com/new_recipes/10saveuserdata.jsp">
197: * http://javacook.darwinsys.com/new_recipes/10saveuserdata.jsp
198: * </a>.
199: *
200: * @return null
201: */
202: @Override
203: protected Void doInBackground() throws IOException {
204: String absPath = file.getAbsolutePath();
205: File tmpFile = new File(absPath + ".tmp");
206: tmpFile.createNewFile();
207: tmpFile.deleteOnExit();
208: File backupFile = new File(absPath + ".bak");
209: BufferedWriter out = null;
210: int fileLength = text.length();
211: int blockSize = Math
212: .max(1024, 1 + ((fileLength - 1) / 100));
213: try {
214: out = new BufferedWriter(new FileWriter(tmpFile));
215: int offset = 0;
216: while (!isCancelled() && (offset < fileLength)) {
217: int length = Math.min(blockSize, fileLength
218: - offset);
219: out.write(text, offset, length);
220: offset += blockSize;
221: setProgress(Math.min(offset, fileLength), 0,
222: fileLength);
223: }
224: } finally {
225: if (out != null) {
226: out.close();
227: }
228: }
229: if (!isCancelled()) {
230: backupFile.delete();
231: if (file.exists()) {
232: renameFile(file, backupFile);
233: }
234: renameFile(tmpFile, file);
235: } else {
236: tmpFile.delete();
237: }
238: return null;
239: }
240: }
241:
242: /**
243: * A Task that loads the contents of a file into a String.
244: */
245: static class LoadTextFileTask extends Task<String, Void> {
246:
247: private final File file;
248:
249: /**
250: * Construct a LoadTextFileTask.
251: *
252: * @param file the file to load from.
253: */
254: LoadTextFileTask(Application application, File file) {
255: super (application);
256: this .file = file;
257: }
258:
259: /**
260: * Return the file being loaded.
261: *
262: * @return the value of the read-only file property.
263: */
264: public final File getFile() {
265: return file;
266: }
267:
268: /**
269: * Load the file into a String and return it. The
270: * {@code progress} property is updated as the file is loaded.
271: * <p>
272: * If this task is cancelled before the entire file has been
273: * read, null is returned.
274: *
275: * @return the contents of the {code file} as a String or null
276: */
277: @Override
278: protected String doInBackground() throws IOException {
279: int fileLength = (int) file.length();
280: int nChars = -1;
281: // progress updates after every blockSize chars read
282: int blockSize = Math.max(1024, fileLength / 100);
283: int p = blockSize;
284: char[] buffer = new char[32];
285: StringBuilder contents = new StringBuilder();
286: BufferedReader rdr = new BufferedReader(
287: new FileReader(file));
288: while (!isCancelled() && (nChars = rdr.read(buffer)) != -1) {
289: contents.append(buffer, 0, nChars);
290: if (contents.length() > p) {
291: p += blockSize;
292: setProgress(contents.length(), 0, fileLength);
293: }
294: }
295: if (!isCancelled()) {
296: return contents.toString();
297: } else {
298: return null;
299: }
300: }
301: }
302: }
|