001/* A program to edit sqlite3 databases 002 * 003 * Copyright (C) 2015 Sidney Marshall (swm@cs.rit.edu) 004 * 005 * This program is free software: you can redistribute it and/or 006 * modify it under the terms of the GNU General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see 017 * <http://www.gnu.org/licenses/>. 018 */ 019 020//import javax.swing.ListCellRenderer; 021//import javax.swing.filechooser.FileFilter; 022 023import java.awt.BorderLayout; 024import java.awt.Color; 025import java.awt.Component; 026import java.awt.Dimension; 027import java.awt.GridLayout; 028import java.awt.Point; 029import java.awt.Rectangle; 030import java.awt.Toolkit; 031import java.awt.datatransfer.DataFlavor; 032import java.awt.datatransfer.StringSelection; 033import java.awt.datatransfer.Transferable; 034import java.awt.datatransfer.UnsupportedFlavorException; 035import java.awt.event.ActionEvent; 036import java.awt.event.ActionListener; 037import java.awt.event.KeyEvent; 038import java.awt.event.MouseAdapter; 039import java.awt.event.MouseEvent; 040import java.awt.event.WindowAdapter; 041import java.awt.event.WindowEvent; 042import java.awt.print.PrinterException; 043import java.io.Closeable; 044import java.io.File; 045import java.io.FileInputStream; 046import java.io.FileOutputStream; 047import java.io.IOException; 048import java.io.OutputStream; 049import java.nio.charset.Charset; 050import java.sql.Connection; 051import java.sql.DriverManager; 052import java.sql.PreparedStatement; 053import java.sql.ResultSet; 054import java.sql.ResultSetMetaData; 055import java.sql.SQLException; 056import java.sql.Statement; 057import java.text.MessageFormat; 058import java.util.ArrayList; 059import java.util.Arrays; 060import java.util.Collections; 061import java.util.List; 062import javax.swing.AbstractAction; 063import javax.swing.ActionMap; 064import javax.swing.Box; 065import javax.swing.BoxLayout; 066import javax.swing.DefaultComboBoxModel; 067import javax.swing.DefaultListCellRenderer; 068import javax.swing.DropMode; 069import javax.swing.InputMap; 070import javax.swing.JButton; 071import javax.swing.JCheckBox; 072import javax.swing.JComboBox; 073import javax.swing.JComponent; 074import javax.swing.JFileChooser; 075import javax.swing.JFrame; 076import javax.swing.JLabel; 077import javax.swing.JList; 078import javax.swing.JPanel; 079import javax.swing.JScrollPane; 080import javax.swing.JTable; 081import javax.swing.JTextArea; 082import javax.swing.JTextField; 083import javax.swing.KeyStroke; 084import javax.swing.ListSelectionModel; 085import javax.swing.ScrollPaneConstants; 086import javax.swing.SwingConstants; 087import javax.swing.SwingUtilities; 088import javax.swing.TransferHandler; 089import javax.swing.border.EmptyBorder; 090import javax.swing.event.DocumentEvent; 091import javax.swing.event.DocumentListener; 092import javax.swing.event.UndoableEditEvent; 093import javax.swing.event.UndoableEditListener; 094import javax.swing.filechooser.FileNameExtensionFilter; 095import javax.swing.table.AbstractTableModel; 096import javax.swing.table.DefaultTableCellRenderer; 097import javax.swing.table.TableCellEditor; 098import javax.swing.table.TableColumn; 099import javax.swing.text.Caret; 100import javax.swing.text.Document; 101import javax.swing.undo.CannotUndoException; 102import javax.swing.undo.UndoManager; 103 104// Note that 'xxx' is a string and "xxx" is an identifier in SQL. 105// could fix this by using illegal character as db/table separator. 106 107/** 108 * This class implements a window displaying the tables and views 109 * contained in a database (or all tables and views in all attached 110 * databases). 111 * 112 * Left clicking on a table name will bring up a table editor. Right 113 * clicking on a table name will bring up a find/replace window that 114 * operates on tables. There are four buttons: 115 * Refresh - refreshes the window contents (needed after adding tables) 116 * Show Hidden Tables - shows sqlite tables and name of database 117 * Open Command Window - opens a command window for sql statements 118 * Open Database - opens a database in a new process 119 * 120 * Types of windows: 121 * database chooser - chooses a database file to edit or view 122 * database editor - list of tables and views in database 123 * table editor - contents of a database table or view or result set 124 * result table viewer - a view on a select statement result 125 * cell editor - editor for a cell in a table, view, or result set 126 * command window - a window for executing sql commands 127 * find/replace - controls finding and replacing on a cell editor 128 * column search window - searches a table column 129 * 130 * All internal operations oprate on strings. When accessing the 131 * database the dbCharset is used for translation to and from the 132 * database to Strings (the second argument on the command line 133 * defaulting to UTF-8). When accessing files the fileCharset is used 134 * to convert file bytes to and from Strings (the third argument on 135 * the command line defaulting to UTF-8). 136 */ 137public class DatabaseEditor extends JFrame { 138 final static long serialVersionUID = 42; 139 140 Connection conn; // the database connection 141 ArrayList<JFrame> dependents = new ArrayList<JFrame>(); 142 //static Charset charset = Charset.availableCharsets().get("windows-1252"); 143 //static Charset charset = Charset.availableCharsets().get("UTF-8"); 144 // null for default character set 145 static Charset dbCharset = Charset.availableCharsets().get("UTF-8"); // use (UTF-8) 146 static Charset fileCharset = Charset.availableCharsets().get("UTF-8"); // use (UTF-8) 147 148 /** 149 * Closes a closable closable file/object ignoring null arguments 150 * and handling exceptions. 151 * 152 * @param file the file/object to be closed 153 */ 154 static void close(Closeable file) { 155 try { 156 if(file != null) file.close(); 157 } catch(IOException e) { 158 e.printStackTrace(); 159 } 160 } 161 162 /** 163 * Gets an item from a ResultSet using the statically defined 164 * character set. 165 * 166 * @param rs the result set to use 167 * @param i the column number to fetch 168 * @return the result string or null 169 * @throws SQLException Throws if problem with SQLite. 170 */ 171 String getWithCharset(ResultSet rs, int i) throws SQLException { 172 //if(dbCharset == null) return rs.getString(i); 173 byte[] data = rs.getBytes(i); 174 if(data == null) return rs.getString(i); // to return possible empty string 175 return new String(data, dbCharset); 176 } 177 178 /** 179 * Sets a ? item in a PreparedStatement from a string using the java 180 * default character set 181 * 182 * This method originally created blobs created by setBytes causing 183 * problems when comparing with numerical values. The commented out 184 * lines exhibit this behavior. The current setString method does 185 * not create blobs. We probably should check and use Strings for 186 * numbers and blobs for non-numbers. 187 * 188 * Could cast the ? to a TEXT via "cast(? as text)" 189 * 190 * @param stat the prepared statement with ? 191 * @param i the index of the ? to be set 192 * @param data the string of data or null 193 * @throws SQLException thrown if problems with SQLite 194 */ 195 void putWithCharset(PreparedStatement stat, int i, String data) throws SQLException { 196 if(data == null) { 197 stat.setNull(i, java.sql.Types.NULL); 198 } else { 199 //byte[] raw = dbCharset == null ? data.getBytes() : data.getBytes(dbCharset); 200 //stat.setBytes(i, raw); 201 stat.setString(i, data); 202 } 203 } 204 205 static final int BUFSIZ = 4096; // used for buffer size for reading 206 207 /** 208 * Read an entire file into a String using fileCharset for 209 * conversion. Note that the conversion must be all at once because 210 * characters may straddle buffer boundaries. Returns null on error. 211 * 212 * @param file the name of the file to be read in 213 * @return the entire contents of the file converted to a String 214 */ 215 String readFileIntoString(File file) { 216 FileInputStream in = null; 217 int totalRead = 0; 218 int numRead; 219 byte[] bytearray = null; 220 byte[] buffer = new byte[BUFSIZ]; 221 try { 222 in = new FileInputStream(file); 223 ArrayList<byte[]> buffers = new ArrayList<byte[]>(); 224 while((numRead = in.read(buffer, 0, BUFSIZ)) > 0) { 225 if(numRead != BUFSIZ) { 226 byte[] temp = new byte[numRead]; 227 System.arraycopy(buffer, 0, temp, 0, numRead); 228 buffer = temp; 229 } 230 buffers.add(buffer); 231 buffer = new byte[BUFSIZ]; 232 totalRead += numRead; 233 } 234 bytearray = new byte[totalRead]; 235 int bufPointer = 0; 236 for(byte[] array : buffers) { 237 int size = array.length; 238 System.arraycopy(array, 0, bytearray, bufPointer, size); 239 bufPointer += size; 240 } 241 } catch(IOException ex) { 242 ex.printStackTrace(); 243 } finally { 244 close(in); 245 } 246 return new String(bytearray, fileCharset); 247 } 248 249 /** 250 * if null return "null" otherwise surround string with the quote 251 * character and escape quote characters with a double quote 252 * character. This will allow using strings in SQL statements and 253 * double quotes for identifiers. 254 * 255 * We now use ? in sql for data - this handles nulls too. 256 * 257 * @param s the String to escape 258 * @param quote the quote character 259 * @return the escaped string 260 */ 261 static String escapeString(String s, char quote) { 262 if(s == null) return "null"; 263 String q1 = Character.toString(quote); 264 String q2 = q1 + q1; 265 return q1 + s.replace(q1, q2) + q1; 266 } 267 268 /** 269 * Finds String in another String ignoring case if ignoreCase is 270 * true. If s1 is null, returns -1. Search starts at fromIndex. To 271 * search the entire string set fromIndex to 0. 272 * 273 * @param ignoreCase true if case is to be ignored 274 * @param s1 String to search 275 * @param s2 String to search for 276 * @param fromIndex location to start search 277 * @return index of s2 in s1 or -1 if unsuccessful 278 */ 279 int findIgnoreCase(boolean ignoreCase, String s1, String s2, int fromIndex) { 280 if(s2.length() == 0) return -1; 281 if(s1 == null) return -1; 282 if(ignoreCase) { 283 int length = s2.length(); 284 for(int i = fromIndex; i <= s1.length() - length; ++i) { 285 if(s1.substring(i, i + length).equalsIgnoreCase(s2)) { 286 return i; 287 } 288 } 289 return -1; 290 } 291 return s1.indexOf(s2, fromIndex); 292 } 293 294 /** 295 * An undoable text edit window 296 * 297 * This is the base class for all of the text windows. Since the 298 * layout is BorderLayout (the default) clients can add buttons or 299 * other things around the text window. The variable textArea can be 300 * used to add listeners to the text area. The number of undos or 301 * redos is unlimited. 302 */ 303 class EditWindow extends JFrame { 304 final static long serialVersionUID = 42; 305 306 ArrayList<JFrame> dependents = new ArrayList<JFrame>(); 307 JTextArea textArea; 308 309 /** 310 * Make a text edit window with given title and 311 * contents. Dependent windows will be closed. The caller is 312 * responsible for displaying the window and adding any 313 * appropriate listeners. 314 * 315 * @param title the title of the window 316 * @param contents the initial contents of the window 317 * @param parent the JFrame on which this window is dependent. 318 */ 319 EditWindow(String title, String contents, JFrame parent) { 320 super(title); 321 addWindowListener(new WindowAdapter() { 322 public void windowClosing(WindowEvent e) { EditWindow.this.dispose(); } 323 public void windowClosed(WindowEvent e) { 324 for(JFrame d : dependents) d.dispose(); 325 if(parent instanceof Table) { 326 Table p = (Table)parent; 327 if(p.dependents.remove(EditWindow.this)) { 328 } else if(DatabaseEditor.this.dependents.remove(EditWindow.this)) { 329 } else { 330 System.out.println("Can't find#1: " + EditWindow.this.getTitle()); 331 } 332 } else if(parent instanceof DatabaseEditor) { 333 DatabaseEditor p = (DatabaseEditor)parent; 334 if(p.dependents.remove(EditWindow.this)) { 335 } else { 336 System.out.println("Can't find#2: " + EditWindow.this.getTitle()); 337 } 338 } else { 339 System.out.println("bad parent: " + parent); 340 } 341 }}); 342 textArea = new UndoableTextArea(contents); 343 textArea.setLineWrap(true); 344 textArea.setWrapStyleWord(true); 345 textArea.addMouseListener(new MouseAdapter() { 346 public void mouseClicked(MouseEvent e) { 347 if(e.getButton() == 3) { 348 JFrame findReplace = new FindReplace(EditWindow.this, true); 349 EditWindow.this.dependents.add(findReplace); 350 findReplace.pack(); 351 findReplace.setVisible(true); 352 } 353 } 354 }); 355 JScrollPane areaScrollPane = new JScrollPane(textArea); 356 setPreferredSize(new Dimension(600, 300)); 357 getContentPane().add(areaScrollPane); 358 } 359 360 /** 361 * Wrap lines in text that are too long. Insert prefix before broken 362 * lines. Delete (some) trailing whitespace 363 * 364 * @param text The text to wrap lines in. 365 * @param maxLineLength the maximum line length desired 366 * @param prefix the prefix for lines that had to be split 367 * @return a String with all lines wrapped 368 */ 369 String wrapLines(String text, int maxLineLength, String prefix) { 370 int maxLength = maxLineLength; 371 int shortLength = maxLineLength - prefix.length(); 372 StringBuilder newText = new StringBuilder(); 373 //String text = textArea.getText(); 374 int textLength = text.length(); 375 while(textLength > 0 && (text.charAt(textLength - 1) == '\n' || 376 text.charAt(textLength - 1) == ' ')) { 377 --textLength; 378 } 379 text = text.substring(0, textLength) + '\n'; 380 int end; 381 382 for(int start = 0 ; start < textLength ; start = end) { 383 end = text.indexOf('\n', start) + 1; 384 if(end == -1) end = textLength; 385 int lineLength = end - start - 1; 386 if(lineLength <= maxLength) { 387 newText.append(text.substring(start, end)); 388 maxLength = maxLineLength; 389 } else { 390 end = text.lastIndexOf(' ', start + maxLength); 391 if(end <= start) end = start + maxLength; 392 newText.append(text.substring(start, end)); 393 newText.append('\n'); 394 newText.append(prefix); 395 maxLength = shortLength; 396 } 397 } 398 return newText.toString(); 399 } 400 401 /** 402 * Break lines in paragaphs respecting the maxLength parameter. 403 * 404 * start processing => 405 * get indent, line length, skip whitespace 406 * set NLin 407 * 408 * letter: add to word 409 * 410 * space: if word != "" 411 * 412 * generally maxLength = 70 413 * 414 * @param maxLength the maximum line length desired 415 */ 416 void breakLines(int maxLength) { 417 String text = textArea.getText(); 418 int start = textArea.getSelectionStart(); 419 int end = textArea.getSelectionEnd(); 420 // Find start of current line - OK if it is -1 421 int in = text.lastIndexOf('\n', start - 1); 422 int lineLength = start - in - 1; 423 StringBuilder newText = new StringBuilder(); 424 StringBuilder indent = new StringBuilder(); 425 int NLin = 0; // number of consecutive NLs 426 // set first indent 427 char c = 0; 428 while(++in < end && (c = text.charAt(in)) <= ' ') { 429 if(c == '\n') { 430 //lineLength = maxLength; 431 indent.setLength(0); 432 NLin = 1; 433 //newText.append('\n'); 434 newText.append(indent); 435 //newText.append("xxxxx"); 436 break; 437 } else { 438 indent.append(c); 439 if(in >= start) { 440 ++lineLength; 441 } 442 } 443 } 444 // first indent is now set 445 // handle first word at beginning of selection 446 in = start; 447 // don't touch if start to middle of word 448 // or if not at end of word 449 // if start in indent 450 while(in < end) { 451 c = text.charAt(in); 452 if(c > ' ') { 453 newText.append(c); 454 ++lineLength; 455 ++in; 456 } else break; 457 } 458 StringBuilder word = new StringBuilder(); 459 /* 460 * State 461 * in - in into input string 462 * lineLength - length of current line 463 * - maxLength if forcing a new line 464 * word - current word being built 465 * newText - replacement text being built 466 * indent - current indentation string 467 * NLin - number of NLs in whitespace 468 * 469 */ 470 //System.out.println("------------------"); 471 while(in < end) { 472 /* 473 System.out.println("in: " + in + 474 " lineLength: " + lineLength + 475 " word: |" + word + 476 "| newText: |" + newText + 477 "| indent: |" + indent + 478 "| NLin: " + NLin); 479 */ 480 c = text.charAt(in++); 481 if(c > ' ') { 482 word.append(c); 483 } else { 484 if(word.length() > 0) { 485 int newLength 486 = lineLength + word.length() + 1; 487 if(newLength < maxLength) { 488 newText.append(' '); 489 lineLength = newLength; 490 } else { 491 newText.append('\n'); 492 newText.append(indent); 493 lineLength = indent.length() + word.length(); 494 } 495 newText.append(word); 496 word.setLength(0); 497 NLin = 0; 498 } 499 if(c == '\n') { 500 ++NLin; 501 if(NLin > 1) { 502 indent.setLength(0); 503 lineLength = maxLength; 504 newText.append('\n'); 505 } 506 } else if(NLin > 1) { 507 indent.append(c); 508 } 509 } 510 } 511 // need to cleanup last word 512 // should get length of word after end 513 // preserve space after last word 514 if(word.length() > 0) { 515 int wl = word.length(); 516 for(int i = end; i < text.length(); i++) { 517 if(text.charAt(i) > ' ') { 518 ++wl; 519 } else break; 520 } 521 int newLength = lineLength + wl + 1; 522 if(newLength < maxLength) { 523 newText.append(' '); 524 lineLength = newLength; 525 } else { 526 newText.append('\n'); 527 newText.append(indent); 528 } 529 newText.append(word); 530 if(c == '\n') newText.append(c); 531 else if(c <= ' ') newText.append(' '); 532 } else { 533 // add ' ' if ??? 534 } 535 textArea.replaceSelection(newText.toString()); 536 end = textArea.getSelectionEnd(); 537 textArea.select(end - newText.length(), end); 538 textArea.grabFocus(); // to show current selection 539 } 540 541 /** 542 * Print the contents of the EditWindow. 543 * //////////// should be in separate thread. 544 * 545 * @param title The title on each page 546 */ 547 void print(String title) { 548 MessageFormat footer = new MessageFormat("Page - {0}"); 549 try { 550 JTextArea ta = new JTextArea(wrapLines(textArea.getText(), 80, ">>>>") + "\n "); // needed to print last line 551 ta.print(new MessageFormat(title), footer); 552 } catch(PrinterException e) { 553 System.out.println(e); 554 } 555 } 556 } // class EditWindow extends JFrame 557 558 /** 559 * This class implements a window displaying the contents of a 560 * database table, view, or result set. The first constructor 561 * displays tables and views and the second constructor displays 562 * result sets. 563 */ 564 class Table extends JFrame { 565 final static long serialVersionUID = 42; 566 567 String tableName; // the name of the table (and title of window) 568 ArrayList<JFrame> dependents = new ArrayList<JFrame>(); 569 JTable table; 570 ArrayList<String> heading; 571 ArrayList<ArrayList<String>> data; // row of columns 572 ArrayList<ArrayList<String>> oldData; // original data saved for possible restore 573 ArrayList<String> rowid; // _rowid_ for each row for updating 574 575 /** 576 * Initialize tables for a table or view with null argument. 577 * Initialize tables for a result set with result set argument. 578 * If called with a null argument then the variable tableName is used 579 * as the table to view. 580 * 581 * Assume that names of databases and tables do not contain "." 582 * 583 * @param rs the result set to view or null if a table or view 584 */ 585 void initTables(ResultSet rs) { 586 boolean flag = rs == null; // true if table; false if result set 587 heading = new ArrayList<String>(); 588 data = new ArrayList<ArrayList<String>>(); 589 oldData = new ArrayList<ArrayList<String>>(); 590 rowid = new ArrayList<String>(); 591 Statement stat = null; 592 try { 593 if(flag) { 594 stat = conn.createStatement(); 595 //views no longer have a _rowid_ so must check 596 try { 597 rs = stat.executeQuery("select _rowid_,* from \"" 598 + tableName.replace(".", "\".\"") 599 + "\";"); 600 } catch(SQLException ex) { 601 rs = stat.executeQuery("select null as _rowid_,* from \"" 602 + tableName.replace(".", "\".\"") 603 + "\";"); 604 } 605 } 606 ResultSetMetaData md = rs.getMetaData(); 607 int n = md.getColumnCount(); 608 609 for(int i = (flag ? 2 : 1); i <= n; i++) { 610 heading.add(md.getColumnName(i)); 611 } 612 while (rs.next()) { 613 rowid.add(flag ? rs.getString(1) : null); 614 ArrayList<String> row = new ArrayList<String>(); 615 ArrayList<String> oldRow = new ArrayList<String>(); 616 for(int i = (flag ? 2 : 1); i <= n; i++) { 617 String s = getWithCharset(rs, i); 618 row.add(s); 619 oldRow.add(s); 620 } 621 data.add(row); 622 oldData.add(oldRow); 623 } 624 } catch(SQLException ex) { 625 ex.printStackTrace(); 626 } finally { 627 try { 628 if(stat != null) stat.close(); 629 } catch(SQLException closeex) { 630 closeex.printStackTrace(); 631 } 632 } 633 } 634 635 /** 636 * Compare two Strings for equality. Also handles nulls. 637 * 638 * @param x first string 639 * @param y second string 640 * @return true if strings are equal or both are null 641 */ 642 boolean eql(String x, String y) { 643 if(x == y) return true; 644 if(x == null || y == null) return false; 645 return x.equals(y); 646 } 647 648 /** 649 * This TableModel gets data from and stores data into the 650 * containing Table class. The model allows drag & drop and 651 * cell editing. The default editor is disabled if there is a 652 * new-line in the string. 653 */ 654 AbstractTableModel tableModel = new AbstractTableModel() { 655 final static long serialVersionUID = 42; 656 657 public int getColumnCount() { 658 return heading.size(); 659 } 660 661 public int getRowCount() { 662 return data.size(); 663 } 664 665 public String getColumnName(int col) { 666 return heading.get(col); 667 } 668 669 public Object getValueAt(int row, int col) { 670 return data.get(row).get(col); 671 } 672 673 /* 674 * JTable uses this method to determine the default renderer/ 675 * editor for each cell. If we didn't implement this method then 676 * the editor might not be a String editor. I always use 677 * Strings. 678 */ 679 public Class<?> getColumnClass(int c) { 680 return String.class; 681 } 682 683 /** 684 * Disable the default editor if the cell contains a new line as 685 * the default editor is not reliable in this case. 686 */ 687 public boolean isCellEditable(int row, int col) { 688 String value = (String)tableModel.getValueAt(row, col); 689 return value == null || value.indexOf('\n') < 0; 690 } 691 692 public void setValueAt(Object value, int row, int col) { 693 if(row < data.size()) { 694 String current = data.get(row).get(col); 695 if(!eql((String)value, current)) { 696 data.get(row).set(col, (String)value); 697 fireTableCellUpdated(row, col); 698 } 699 } 700 } 701 }; 702 703 /** 704 * Create an edit window on a Table cell and update table focus to 705 * edited cell. Caller is responsible for setting location and 706 * showing the window. Don't search for an existing cellEditWindow 707 * if rowid is null. 708 * 709 * @param jtable the table containing the cell to be edited 710 * @param i the row of the Table being edited 711 * @param jj the column of the Table being edited 712 * @param location the location to put the CellEditWindow 713 * @return returns the new EditWindow 714 */ 715 EditWindow makeCellEditWindow(Table jtable, int i, int jj, Rectangle location) { 716 table.changeSelection(i, jj, false, false); // update focus 717 int j = jtable.table.convertColumnIndexToModel(jj); 718 String id = rowid.get(i); // used in case rows are deleted 719 String title = tableName + "." + heading.get(j) 720 + "[" + id + "]"; 721 if(id != null) { 722 for(JFrame jf : jtable.dependents) { 723 if(jf.getTitle().equals(title)) { 724 EditWindow editw = (EditWindow)jf; 725 editw.setExtendedState(JFrame.NORMAL); 726 editw.setVisible(true); 727 return editw; 728 } 729 } 730 } 731 EditWindow editWindow = new EditWindow(title, (String)tableModel.getValueAt(i, j), jtable); 732 JTextArea textArea = editWindow.textArea; 733 DocumentListener documentListener = new DocumentListener() { 734 Color color = Color.white; 735 736 void checkForChanges() { 737 String window = textArea.getText(); 738 String table = (String)tableModel.getValueAt(i, j); 739 Color temp = eql(window, table) ? Color.white : Color.pink; 740 if(!color.equals(temp)) { 741 textArea.setBackground(temp); 742 color = temp; 743 } 744 } 745 746 public void changedUpdate(DocumentEvent e) { 747 checkForChanges(); 748 }; 749 750 public void insertUpdate(DocumentEvent e) { 751 checkForChanges(); 752 }; 753 754 public void removeUpdate(DocumentEvent e) { 755 checkForChanges(); 756 }; 757 }; 758 textArea.getDocument().addDocumentListener(documentListener); 759 JPanel btns = new JPanel(new GridLayout(id != null ? 4 : 3, 1)); 760 btns.add(new JButton("Open in Command Window") { 761 final static long serialVersionUID = 42; 762 763 { 764 addActionListener(new ActionListener() { 765 public void actionPerformed(ActionEvent e) { 766 textArea.grabFocus(); // to show current selection 767 makeCommandWindow("Data Command Window", 768 textArea.getText()); 769 } 770 }); 771 } 772 }); 773 btns.add(new JButton("Break Lines in Selection") { 774 final static long serialVersionUID = 42; 775 776 { 777 addActionListener(new ActionListener() { 778 public void actionPerformed(ActionEvent e) { 779 editWindow.breakLines(70); 780 } 781 }); 782 } 783 }); 784 btns.add(new JButton("Print") { 785 final static long serialVersionUID = 42; 786 787 { 788 addActionListener(new ActionListener() { 789 public void actionPerformed(ActionEvent e) { 790 editWindow.print(editWindow.getTitle()); 791 } 792 }); 793 } 794 }); 795 if(id != null) { 796 btns.add(new JButton("Update") { 797 final static long serialVersionUID = 42; 798 799 { 800 addActionListener(new ActionListener() { 801 public void actionPerformed(ActionEvent e) { 802 String s = textArea.getText(); 803 int newi = rowid.indexOf(id); 804 if(newi >= 0) { 805 tableModel.setValueAt(s, newi, j); 806 documentListener.changedUpdate(null); 807 } 808 textArea.grabFocus(); // to show current selection 809 } 810 }); 811 } 812 }); 813 } 814 editWindow.getContentPane().add(btns, BorderLayout.SOUTH); 815 if(id != null) Table.this.dependents.add(editWindow); // if result set then keep longer 816 else DatabaseEditor.this.dependents.add(editWindow); // if table or view then depend on table 817 if(location != null) { 818 editWindow.setLocation(location.x, location.y + location.height); 819 editWindow.pack(); 820 editWindow.setVisible(true); 821 } else { 822 editWindow.setLocationByPlatform(true); 823 editWindow.pack(); 824 editWindow.setVisible(true); 825 } 826 return editWindow; 827 } 828 829 /** 830 * This is the JPanel displaying the database table, view, or 831 * result set. Left clicking on a cell will select it. Double 832 * clicking on a cell will enable the cell editor if the cell does 833 * not contain new lines. Right clicking on a cell will bring up a 834 * cell editor window. Right clicking on a column label will bring 835 * up a find/replace window on the column. 836 */ 837 class DBTable extends JPanel { 838 final static long serialVersionUID = 42; 839 840 /** 841 * Constructor for displaying a table, view, or ResultSet. With 842 * a null argument this will display the table names in the 843 * variable tableName. 844 * 845 * @param rs ResultSet to display or null if table of view 846 */ 847 public DBTable(ResultSet rs) { 848 super(new GridLayout(1,0)); 849 850 setOpaque(true); 851 852 initTables(rs); 853 854 table = new JTable(tableModel); 855 table.setAutoCreateColumnsFromModel(true); 856 table.setPreferredScrollableViewportSize(new Dimension(500, 70)); 857 table.setFillsViewportHeight(true); 858 table.setRowSelectionAllowed(false); 859 table.setColumnSelectionAllowed(false); 860 table.setCellSelectionEnabled(true); 861 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 862 table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 863 table.setDragEnabled(true); 864 table.setDropMode(DropMode.ON); 865 table.setDefaultRenderer(String.class, 866 new DefaultTableCellRenderer() { 867 final static long serialVersionUID = 42; 868 869 /** 870 * This class controls the background color for table cells. 871 */ 872 public Component getTableCellRendererComponent(JTable table, 873 Object value, 874 boolean isSelected, 875 boolean hasFocus, 876 int row, 877 int viewColumn) { 878 if (table == null) { 879 return this; 880 } 881 882 int column = table.convertColumnIndexToModel(viewColumn); 883 884 Color fg = null; 885 Color bg = null; 886 887 String val = data.get(row).get(column); 888 boolean changed = !eql(oldData.get(row).get(column), val); 889 890 JTable.DropLocation dropLocation = table.getDropLocation(); 891 if (dropLocation != null 892 && !dropLocation.isInsertRow() 893 && !dropLocation.isInsertColumn() 894 && dropLocation.getRow() == row 895 && table.convertColumnIndexToModel(dropLocation.getColumn()) == column) { 896 fg = Color.blue; 897 bg = Color.pink; 898 isSelected = true; 899 } 900 901 if (isSelected) { 902 super.setForeground(fg == null 903 ? table.getSelectionForeground() 904 : fg); 905 super.setBackground(bg == null 906 ? !changed 907 ? table.getSelectionBackground() 908 : Color.red 909 : bg); 910 } else { 911 Color background = table.getBackground(); 912 if(changed) { 913 background = Color.pink; 914 } else if (val == null) { 915 background = Color.gray; 916 } 917 super.setForeground(table.getForeground()); 918 super.setBackground(background); 919 } 920 setFont(table.getFont()); 921 setBorder(new EmptyBorder(1, 1, 1, 1)); 922 setValue(value); 923 return this; 924 } 925 }); 926 table.setTransferHandler(new TransferHandler() { 927 final static long serialVersionUID = 42; 928 929 /** 930 * This class handles drag & drop. Strings and files can 931 * be dropped. For files only the file name without 932 * directory information is retained. Dropping multiple 933 * files is not supported. 934 */ 935 public boolean canImport(TransferHandler.TransferSupport info) { 936 if (!info.isDrop()) return false; // no clipboard past 937 info.setShowDropLocation(true); 938 if (info.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { 939 // fetch the drop location 940 JTable.DropLocation dl = (JTable.DropLocation)info.getDropLocation(); 941 int row = dl.getRow(); 942 int col = table.convertColumnIndexToModel(dl.getColumn()); 943 if(col < 0) return false; 944 return true; 945 } 946 if (info.isDataFlavorSupported(DataFlavor.stringFlavor)) { 947 JTable.DropLocation dl = (JTable.DropLocation)info.getDropLocation(); 948 int row = dl.getRow(); 949 int col = table.convertColumnIndexToModel(dl.getColumn()); 950 if(col < 0) return false; 951 return true; 952 } 953 return false; 954 } 955 956 public boolean importData(TransferHandler.TransferSupport info) { 957 if (!canImport(info)) { 958 return false; 959 } 960 JTable.DropLocation dl = (JTable.DropLocation)info.getDropLocation(); 961 int row = dl.getRow(); 962 int col = table.convertColumnIndexToModel(dl.getColumn()); 963 String data; 964 try { 965 if(info.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { 966 List<?> files = (List<?>)info.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); 967 if(files.size() != 1) return false; // can handle only one file 968 data = ((File)files.get(0)).getName(); 969 } else if(info.isDataFlavorSupported(DataFlavor.stringFlavor)) { 970 data = (String)info.getTransferable().getTransferData(DataFlavor.stringFlavor); 971 } else { 972 return false; 973 } 974 } catch(UnsupportedFlavorException | IOException e) { 975 return false; 976 } 977 table.getModel().setValueAt(data, row, col); 978 table.changeSelection(row, dl.getColumn(), false, false); // update focus 979 return true; 980 } 981 982 public int getSourceActions(JComponent c) { 983 return COPY; 984 } 985 986 public Transferable createTransferable(JComponent c) { 987 return new StringSelection(data.get(table.getSelectedRow()).get(table.convertColumnIndexToModel(table.getSelectedColumn()))); 988 } 989 990 public void exportDone(JComponent c, Transferable t, int i) { 991 } 992 }); 993 table.getTableHeader().addMouseListener(new MouseAdapter() { 994 public void mouseClicked(MouseEvent e) { 995 if(e.getButton() == 3) { 996 Point p = e.getPoint(); 997 int jj = table.columnAtPoint(p); 998 int j = table.convertColumnIndexToModel(jj); 999 String columnName = Table.this.heading.get(j); 1000 Search search = new Search(Table.this); 1001 DefaultComboBoxModel<String> model = (DefaultComboBoxModel<String>)search.columnField.getModel(); 1002 model.setSelectedItem(columnName); 1003 Table.this.dependents.add(search); 1004 Rectangle r = Table.this.getBounds(); 1005 search.setLocation(r.x + r.width, r.y); 1006 search.tableField.setText(tableName); 1007 search.pack(); 1008 search.setVisible(true); 1009 } 1010 } 1011 }); 1012 table.addMouseListener(new MouseAdapter() { 1013 public void mouseClicked(MouseEvent e) { 1014 if(e.getButton() == 3) { 1015 Point p = e.getPoint(); 1016 int i = table.rowAtPoint(p); 1017 int jj = table.columnAtPoint(p); 1018 EditWindow cellEditor = makeCellEditWindow(Table.this, i, jj, null); 1019 } 1020 } 1021 }); 1022 for (int i = 0; i < heading.size(); i++) { 1023 TableColumn column = table.getColumnModel().getColumn(i); 1024 column.setPreferredWidth(100); 1025 } 1026 ((DefaultTableCellRenderer)table.getTableHeader().getDefaultRenderer()) 1027 .setHorizontalAlignment(SwingConstants.LEFT); 1028 1029 JScrollPane scrollPane = 1030 new JScrollPane(table, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, 1031 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); 1032 add(scrollPane); 1033 } 1034 } // class DBTable extends JPanel 1035 1036 /** 1037 * For displaying tables or views 1038 * 1039 * @param tableName the title of the window and table or view to display 1040 */ 1041 Table(String tableName) { 1042 this.tableName = tableName; 1043 DatabaseEditor.this.dependents.add(this); 1044 addWindowListener(new WindowAdapter() { 1045 public void windowClosing(WindowEvent e) { dispose(); } 1046 public void windowClosed(WindowEvent e) { 1047 for(JFrame d : dependents) d.dispose(); 1048 if(DatabaseEditor.this.dependents.remove(Table.this)) { 1049 } else { 1050 System.out.println("Can't find#3: " + Table.this.getTitle()); 1051 } 1052 } 1053 }); 1054 setTitle(tableName); 1055 getContentPane().add(new DBTable(null)); 1056 JPanel buttons = new JPanel(new GridLayout(2, 4)); 1057 buttons.add(new JButton("Delete Selected Rows") { 1058 final static long serialVersionUID = 42; 1059 1060 { 1061 addActionListener(new ActionListener() { 1062 public void actionPerformed(ActionEvent e) { 1063 TableCellEditor te = table.getCellEditor(); 1064 if(te != null) te.stopCellEditing(); 1065 deleteRows(); 1066 } 1067 }); 1068 } 1069 }); 1070 buttons.add(new JButton("Make NULL") { 1071 final static long serialVersionUID = 42; 1072 1073 { 1074 addActionListener(new ActionListener() { 1075 public void actionPerformed(ActionEvent e) { 1076 TableCellEditor te = table.getCellEditor(); 1077 if(te != null) te.stopCellEditing(); 1078 makeNull(); 1079 } 1080 }); 1081 } 1082 }); 1083 buttons.add(new JButton("Revert Selected") { 1084 final static long serialVersionUID = 42; 1085 1086 { 1087 addActionListener(new ActionListener() { 1088 public void actionPerformed(ActionEvent e) { 1089 TableCellEditor te = table.getCellEditor(); 1090 if(te != null) te.stopCellEditing(); 1091 revertSelected(); 1092 } 1093 }); 1094 } 1095 }); 1096 buttons.add(new JButton("Revert Table") { 1097 final static long serialVersionUID = 42; 1098 1099 { 1100 addActionListener(new ActionListener() { 1101 public void actionPerformed(ActionEvent e) { 1102 TableCellEditor te = table.getCellEditor(); 1103 if(te != null) te.stopCellEditing(); 1104 revertTable(); 1105 } 1106 }); 1107 } 1108 }); 1109 buttons.add(new JButton("Add Row") { 1110 final static long serialVersionUID = 42; 1111 1112 { 1113 addActionListener(new ActionListener() { 1114 public void actionPerformed(ActionEvent e) { 1115 TableCellEditor te = table.getCellEditor(); 1116 if(te != null) te.stopCellEditing(); 1117 addRow(); 1118 } 1119 }); 1120 } 1121 }); 1122 buttons.add(new JButton("Fill Column") { 1123 final static long serialVersionUID = 42; 1124 1125 { 1126 addActionListener(new ActionListener() { 1127 public void actionPerformed(ActionEvent e) { 1128 int i = table.getSelectedRow(); 1129 int j = table.convertColumnIndexToModel(table.getSelectedColumn()); 1130 if(i < 0 || j < 0) return; 1131 if(data.get(i).get(j) == null) { 1132 if(i == 0) return; 1133 if(data.get(i - 1).get(j) != null) { 1134 fillColumn(i, j); 1135 } else { 1136 return; 1137 } 1138 } else if(i < data.size() - 1 && data.get(i + 1).get(j) == null) { 1139 fillColumn(i + 1, j); 1140 } else { 1141 if(i < data.size() - 2 && data.get(i + 2).get(j) == null) { 1142 fillColumn(i + 2, j, data.get(i).get(j), data.get(i + 1).get(j)); 1143 } 1144 } 1145 } 1146 }); 1147 } 1148 }); 1149 buttons.add(new JButton("Update Selected") { 1150 final static long serialVersionUID = 42; 1151 1152 { 1153 addActionListener(new ActionListener() { 1154 public void actionPerformed(ActionEvent e) { 1155 TableCellEditor te = table.getCellEditor(); 1156 if(te != null) te.stopCellEditing(); 1157 updateSelected(); 1158 } 1159 }); 1160 } 1161 }); 1162 buttons.add(new JButton("Update Table") { 1163 final static long serialVersionUID = 42; 1164 1165 { 1166 addActionListener(new ActionListener() { 1167 public void actionPerformed(ActionEvent e) { 1168 TableCellEditor te = table.getCellEditor(); 1169 if(te != null) te.stopCellEditing(); 1170 updateTable(); 1171 } 1172 }); 1173 } 1174 }); 1175 getContentPane().add(buttons, BorderLayout.SOUTH); 1176 setLocationByPlatform(true); 1177 } 1178 1179 /** 1180 * For displaying result sets 1181 * 1182 * @param rs the ResultSet to display 1183 * @param tableName the title of the window 1184 */ 1185 Table(ResultSet rs, String tableName) { 1186 this.tableName = tableName; 1187 DatabaseEditor.this.dependents.add(this); 1188 addWindowListener(new WindowAdapter() { 1189 public void windowClosing(WindowEvent e) { dispose(); } 1190 public void windowClosed(WindowEvent e) { 1191 for(JFrame d : dependents) d.dispose(); 1192 if(DatabaseEditor.this.dependents.remove(Table.this)) { 1193 } else { 1194 System.out.println("Can't find#4: " + Table.this.getTitle()); 1195 } 1196 } 1197 }); 1198 setTitle(tableName); 1199 getContentPane().add(new DBTable(rs)); 1200 JLabel tableLabel = new JLabel("<html>Table:"); 1201 JTextField tableField = new JTextField("temp", 10); 1202 JTextField fileField = new JTextField("data.txt", 10); 1203 JTextArea columnArea = new UndoableTextArea(","); 1204 JTextArea rowArea = new UndoableTextArea("\n"); 1205 JTextArea nullArea = new UndoableTextArea("null"); 1206 JCheckBox checkBox = new JCheckBox("<html><font size=-2>Escape Strings</font>"); 1207 JButton writeButton = new JButton("<html>Write File:") { 1208 final static long serialVersionUID = 42; 1209 1210 { 1211 addActionListener(new ActionListener() { 1212 public void actionPerformed(ActionEvent e) { 1213 TableCellEditor te = table.getCellEditor(); 1214 if(te != null) te.stopCellEditing(); 1215 outputTable(fileField.getText(), 1216 columnArea.getText(), 1217 rowArea.getText(), 1218 nullArea.getText(), 1219 checkBox.getModel().isSelected()); 1220 } 1221 }); 1222 } 1223 }; 1224 JButton browseButton = new JButton("<html><font size=-2>Browse</font>") { 1225 final static long serialVersionUID = 42; 1226 1227 { 1228 addActionListener(new ActionListener() { 1229 public void actionPerformed(ActionEvent e) { 1230 File file = chooseFile("set", new String[][]{}); 1231 if(file == null) return; 1232 String fileName = file.getPath(); 1233 if(fileName != null) fileField.setText(fileName); 1234 } 1235 }); 1236 } 1237 }; 1238 JLabel columnLabel = new JLabel("<html>Column Separator:"); 1239 JScrollPane columnScrollPane 1240 = new JScrollPane(columnArea, 1241 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 1242 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); 1243 columnScrollPane.setPreferredSize(new Dimension(100, 60)); 1244 JLabel rowLabel = new JLabel("<html>Row End:"); 1245 JScrollPane rowScrollPane 1246 = new JScrollPane(rowArea, 1247 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 1248 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); 1249 rowScrollPane.setPreferredSize(new Dimension(100, 60)); 1250 JLabel nullLabel = new JLabel("<html>null:"); 1251 JScrollPane nullScrollPane 1252 = new JScrollPane(nullArea, 1253 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 1254 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); 1255 nullScrollPane.setPreferredSize(new Dimension(80, 60)); 1256 1257 Box panel = new Box(BoxLayout.X_AXIS); 1258 1259 Box filePanel = new Box(BoxLayout.Y_AXIS); 1260 panel.add(Box.createHorizontalStrut(5)); 1261 1262 filePanel.add(writeButton); 1263 filePanel.add(fileField); 1264 Box browseEscapePanel = new Box(BoxLayout.X_AXIS); 1265 browseEscapePanel.add(browseButton); 1266 browseEscapePanel.add(checkBox); 1267 browseEscapePanel.setAlignmentX(0.0f); 1268 filePanel.add(browseEscapePanel); 1269 panel.add(filePanel); 1270 1271 panel.add(Box.createHorizontalStrut(5)); 1272 1273 Box columnPanel = new Box(BoxLayout.Y_AXIS); 1274 columnPanel.add(columnLabel); 1275 columnPanel.add(columnScrollPane); 1276 columnPanel.add(Box.createVerticalGlue()); 1277 panel.add(columnPanel); 1278 1279 panel.add(Box.createHorizontalStrut(5)); 1280 1281 Box rowPanel = new Box(BoxLayout.Y_AXIS); 1282 rowPanel.add(rowLabel); 1283 rowPanel.add(rowScrollPane); 1284 rowPanel.add(Box.createVerticalGlue()); 1285 panel.add(rowPanel); 1286 1287 panel.add(Box.createHorizontalStrut(5)); 1288 1289 Box nullPanel = new Box(BoxLayout.Y_AXIS); 1290 nullPanel.add(nullLabel); 1291 nullPanel.add(nullScrollPane); 1292 nullPanel.add(Box.createVerticalGlue()); 1293 nullPanel.setMaximumSize(nullPanel.getPreferredSize()); 1294 panel.add(nullPanel); 1295 1296 panel.add(Box.createHorizontalGlue()); 1297 1298 getContentPane().add(panel, BorderLayout.SOUTH); 1299 setLocationByPlatform(true); 1300 } 1301 1302 /** 1303 * Output a result set to a text file separating rows by a new 1304 * line and cells in a row by the separator String. 1305 * 1306 * @param fileName the name of the file to write 1307 * @param column the cell separator 1308 * @param row the row cell separator 1309 * @param nil the String used for a null entry 1310 * @param escape true if replace ' with '' 1311 */ 1312 void outputTable(String fileName, String column, String row, String nil, boolean escape) { 1313 FileOutputStream out = null; 1314 try { 1315 out = new FileOutputStream(fileName); 1316 for(ArrayList<String> a : data) { 1317 for(int i = 0; i < a.size(); i++) { 1318 if(i != 0) out.write(column.getBytes(fileCharset)); 1319 String s = a.get(i); // .getBytes(fileCharset); 1320 if(s == null) { 1321 out.write(nil.getBytes(fileCharset)); 1322 } else { 1323 out.write((escape ? escapeString(s, '\'') : s).getBytes(fileCharset)); 1324 } 1325 } 1326 out.write(row.getBytes(fileCharset)); 1327 } 1328 } catch(IOException e) { 1329 e.printStackTrace(); 1330 } finally { 1331 close(out); 1332 } 1333 } 1334 1335 /** 1336 * Delete all of the rows that are selected. Then reloads the 1337 * table from the database. Currently only a single cell can be 1338 * selected so this method deletes a single row. Note that this 1339 * method throws away all pending edits that have not been 1340 * committed. 1341 */ 1342 void deleteRows() { 1343 Statement stat = null; 1344 try { 1345 stat = conn.createStatement(); 1346 for(int i : table.getSelectedRows()) { 1347 int count = stat.executeUpdate("delete from \"" 1348 + tableName.replace(".", "\".\"") 1349 + "\" where (_rowid_ = " + rowid.get(i) 1350 + ");"); 1351 // count = 1 for both tables and views ??? 1352 tableModel.fireTableRowsDeleted(i, i); // is this needed? 1353 } 1354 initTables(null); // update table from database 1355 tableModel.fireTableDataChanged(); 1356 } catch(SQLException ex) { 1357 ex.printStackTrace(); 1358 } finally { 1359 try{ 1360 if(stat != null) stat.close(); 1361 } catch(SQLException closeex) { 1362 closeex.printStackTrace(); 1363 } 1364 } 1365 } 1366 1367 /** 1368 * This method sets the selected cell to null 1369 */ 1370 void makeNull() { 1371 int i = table.getSelectedRow(); 1372 int j = table.convertColumnIndexToModel(table.getSelectedColumn()); 1373 if(i < 0 || j < 0) return; 1374 ArrayList<String> row = data.get(i); 1375 row.set(j, null); 1376 tableModel.fireTableCellUpdated(i, j); 1377 } 1378 1379 /** 1380 * This method undoes all of the uncommitted edits in the selected 1381 * row. Currently only one cell/row can be selected. 1382 */ 1383 void revertSelected() { 1384 int r[] = table.getSelectedRows(); 1385 for(int i : r) { 1386 ArrayList<String> row = data.get(i); 1387 ArrayList<String> oldrow = oldData.get(i); 1388 for(int j = 0; j < row.size(); j++) { 1389 String old = oldrow.get(j); 1390 if(!eql(old, row.get(j))) { 1391 row.set(j, old); 1392 tableModel.fireTableCellUpdated(i, j); 1393 } 1394 } 1395 } 1396 } 1397 1398 /** 1399 * This method undoes all of the uncommitted edits in the entire 1400 * table. 1401 */ 1402 void revertTable() { 1403 for(int i = 0; i < data.size(); i++) { 1404 ArrayList<String> row = data.get(i); 1405 ArrayList<String> oldrow = oldData.get(i); 1406 for(int j = 0; j < row.size(); j++) { 1407 String old = oldrow.get(j); 1408 if(!eql(old, row.get(j))) { 1409 row.set(j, old); 1410 tableModel.fireTableCellUpdated(i, j); 1411 } 1412 } 1413 } 1414 } 1415 1416 /** 1417 * This method adds a new row to the table. All cells in this new 1418 * row are set to null. If at least one entry is not set non-null 1419 * then this row will not be added to the table. To add an all 1420 * null row first add a row with a cell not null and then edit 1421 * this cell to null (by dragging a null cell value to it). 1422 */ 1423 void addRow() { 1424 int oldSize = data.size(); 1425 data.add(new ArrayList<String>(Collections.nCopies(heading.size(), 1426 (String)null))); 1427 oldData.add(new ArrayList<String>(Collections.nCopies(heading.size(), 1428 (String)null))); 1429 rowid.add(null); 1430 tableModel.fireTableRowsInserted(oldSize, data.size() - 1); 1431 table.scrollRectToVisible(table.getCellRect(oldSize, -1, true)); 1432 } 1433 1434 /** 1435 * This method fills in all cells from cell[i, j] to the end of 1436 * the table stopping at the first nonnull entry 1437 * 1438 * @param i the row number of the first cell to be filled 1439 * @param j the column number of the first cell to be filled 1440 */ 1441 void fillColumn(int i, int j) { 1442 String s = data.get(i - 1).get(j); 1443 int size = data.size(); 1444 while(i < size && data.get(i).get(j) == null) { 1445 data.get(i).set(j, s); 1446 tableModel.fireTableCellUpdated(i, j); 1447 ++i; 1448 } 1449 } 1450 1451 /** 1452 * This method fills in all cells from cell[i, j] to the end of 1453 * the table stopping at the first nonnull entry. It requires that 1454 * s1 and s2 are Strings of equal length and differ in only one 1455 * character. The differing character is continually incremented 1456 * by the difference between the two differing characters and the 1457 * resulting patched String isinserted in successive cells. This 1458 * makes the successive cells in arithmetic progression. No checks 1459 * are made for generating illegal characters. 1460 * 1461 * @param i the row number of the first cell to be filled 1462 * @param j the column number of the first cell to be filled 1463 * @param s1 the first string in sequence 1464 * @param s2 the second string in sequence 1465 */ 1466 void fillColumn(int i, int j, String s1, String s2) { 1467 int length = s1.length(); 1468 if(length != s2.length()) return; 1469 int k; 1470 for(k = 0; k < length; ++k) { 1471 if(s1.charAt(k) != s2.charAt(k)) break; 1472 } 1473 if(k == length) { 1474 fillColumn(i + 1, j); 1475 return; 1476 } 1477 char c1 = s1.charAt(k); 1478 char c2 = s2.charAt(k); 1479 int cdiff = c2 - c1; 1480 for(int kk = k + 1; kk < length; ++kk) { 1481 if(s1.charAt(kk) != s2.charAt(kk)) return; 1482 } 1483 // OK now do it 1484 String front = s1.substring(0, k); 1485 String back = s1.substring(k + 1); 1486 char cx = c2; 1487 1488 int size = data.size(); 1489 while(i < size && data.get(i).get(j) == null) { 1490 cx = (char)(cx + cdiff); 1491 String s = front + cx + back; 1492 data.get(i).set(j, s); 1493 tableModel.fireTableCellUpdated(i, j); 1494 ++i; 1495 } 1496 } 1497 1498 /** 1499 * This method updates all cells in the selected row. 1500 */ 1501 void updateSelected() { 1502 int r[] = table.getSelectedRows(); 1503 for(int i : r) { 1504 updateRow(i); 1505 tableModel.fireTableRowsUpdated(i, i); 1506 } 1507 } 1508 1509 /** 1510 * This method updates all cells in the entire table. 1511 */ 1512 void updateTable() { 1513 for(int i = 0; i < data.size(); i++) { 1514 updateRow(i); 1515 } 1516 initTables(null); 1517 tableModel.fireTableDataChanged(); 1518 } 1519 1520 /** 1521 * New strategy is to use ? instead of putting new values in the 1522 * statement itself. There are row.size() ? marks and the same 1523 * number of putWithCharset(PreparedStatement stat, int i, String 1524 * data) calls. The advantage of the new way is that the character 1525 * encoding can be changed easily (and from the command line) 1526 * although this is currently disables because of the difficulty 1527 * of using blobs. Also avoids sql injection attacks. 1528 * 1529 * This method updates the specified row 1530 * 1531 * @param i the row number to update 1532 */ 1533 void updateRow(int i) { 1534 PreparedStatement stat = null; 1535 try { 1536 // rowid = null is insert 1537 ArrayList<String> row = data.get(i); 1538 ArrayList<String> oldRow = oldData.get(i); 1539 String id = rowid.get(i); 1540 if(id == null) { // add row 1541 String colNames = ""; 1542 String colValues = ""; 1543 for(int j = 0; j < row.size(); j++) { 1544 String old = oldRow.get(j); 1545 if(!eql(old, row.get(j))) { 1546 String h = '"' + heading.get(j) + '"'; 1547 colNames += (colNames == "" ? h : "," + h); //////// fails on group as a column name - must use (')s around row name 1548 // "cast(? as text)" 1549 colValues += (colValues == "" ? "?" : ",?"); 1550 } 1551 } 1552 if(!colNames.equals("")) { 1553 stat = conn.prepareStatement("insert into \"" 1554 + tableName.replace(".", "\".\"") 1555 + "\"(" + colNames 1556 + ") values (" + colValues + ");"); 1557 int jj = 1; 1558 for(int j = 0; j < row.size(); j++) { 1559 if(!eql(oldRow.get(j), row.get(j))) { 1560 putWithCharset(stat, jj++, row.get(j)); 1561 } 1562 } 1563 int count = stat.executeUpdate(); 1564 //count = 1 for normal table and count = 0 for view update 1565 for(int j = 0; j < row.size(); j++) { 1566 oldRow.set(j, row.get(j)); 1567 } 1568 } 1569 } else { // update row 1570 for(int j = 0; j < row.size(); j++) { 1571 String newData = row.get(j); 1572 if(!eql(oldRow.get(j), newData)) { 1573 // "cast(? as text)" 1574 stat = conn.prepareStatement("update \"" 1575 + tableName.replace(".", "\".\"") 1576 + "\" set " + heading.get(j) 1577 + " = ? where _rowid_ = " 1578 + rowid.get(i) 1579 + ";"); 1580 putWithCharset(stat, 1, row.get(j)); 1581 int count = stat.executeUpdate(); 1582 //count = 1 for normal table and count = 0 for view update 1583 oldRow.set(j, newData); 1584 } 1585 } 1586 } 1587 } catch(SQLException ex) { 1588 ex.printStackTrace(); 1589 } finally { 1590 try { 1591 if (stat != null) stat.close(); 1592 } catch(SQLException closeex) { 1593 closeex.printStackTrace(); 1594 } 1595 } 1596 tableModel.fireTableRowsUpdated(i, i); 1597 } 1598 } // class Table extends JFrame 1599 1600 /** 1601 * Show the table with the given name 1602 * 1603 * @param tableName the table to display 1604 */ 1605 void showTable(String tableName) { 1606 Table frame; 1607 search: { 1608 for(JFrame jf : dependents) { 1609 if(jf.getTitle().equals(tableName)) { 1610 frame = (Table)jf; 1611 frame.setExtendedState(JFrame.NORMAL); 1612 break search; 1613 } 1614 } 1615 frame = new Table(tableName); 1616 frame.pack(); 1617 frame.setVisible(true); 1618 } 1619 frame.setVisible(true); 1620 return; 1621 } 1622 1623 File editFile = new File(".", "temp.txt"); // default edit text file 1624 1625 /** 1626 * Make a command window for executing sql statements. The Execute 1627 * Selection button will execute the command in the window or the 1628 * command in the current selection if the current selection is not 1629 * empty. 1630 * 1631 * The Execute Selection button executes the current selection as an 1632 * SQL command. If there is no selection the entire contents of the 1633 * window is executed. 1634 * 1635 * The Select Next Command button selects the sql command following 1636 * the current selection. 1637 * 1638 * The Break Lines in Selection button breaks long line in the 1639 * current selection. 1640 * 1641 * The Print button prints the edit window to a printer. 1642 * 1643 * The Read File button reads the selected file and replaces the 1644 * window contents with the contents of the file. This operation is 1645 * undoable. 1646 * 1647 * The Write File button writes the window contents to the selected 1648 * file. 1649 * 1650 * @param title the title of the window 1651 * @param contents the initial contents of the command window 1652 */ 1653 void makeCommandWindow(String title, String contents) { 1654 EditWindow commandWindow = new EditWindow(title, contents, DatabaseEditor.this); 1655 dependents.add(commandWindow); // to database table window 1656 1657 JTextArea textArea = commandWindow.textArea; 1658 JPanel inner = new JPanel(new BorderLayout()); 1659 JPanel btns = new JPanel(new GridLayout(4,1)); 1660 JLabel status = new JLabel("(status line)"); 1661 btns.add(new JButton("Execute Selection") { 1662 final static long serialVersionUID = 42; 1663 1664 { 1665 addActionListener(new ActionListener() { 1666 public void actionPerformed(ActionEvent e) { 1667 Statement stat = null; 1668 try { 1669 String s = textArea.getSelectedText(); 1670 if(s == null) s = textArea.getText(); 1671 stat = conn.createStatement(); 1672 stat.execute(s); 1673 int count = stat.getUpdateCount(); 1674 if(count >= 0) { 1675 status.setText("Update count = " + count); 1676 } else { 1677 ResultSet rs = stat.getResultSet(); 1678 if(rs != null) { 1679 status.setText("Result Set Generated"); 1680 JFrame frame = new Table(rs, "SQL Command Result"); 1681 frame.pack(); 1682 frame.setVisible(true); 1683 } 1684 } 1685 } catch(SQLException ex) { 1686 status.setText(ex.toString()); 1687 } finally { 1688 try { 1689 if(stat != null) stat.close(); 1690 } catch(SQLException closeex) { 1691 closeex.printStackTrace(); 1692 } 1693 } 1694 textArea.grabFocus(); 1695 } 1696 }); 1697 } 1698 }); 1699 btns.add(new JButton("Select Next Command") { 1700 final static long serialVersionUID = 42; 1701 1702 { 1703 addActionListener(new ActionListener() { 1704 public void actionPerformed(ActionEvent e) { 1705 String s = textArea.getText(); 1706 int end = textArea.getSelectionEnd(); 1707 int statementEnd = getEndOfSqlStatement(s, end); 1708 textArea.select(end, statementEnd); 1709 Caret caret = textArea.getCaret(); 1710 caret.setVisible(true); 1711 caret.setSelectionVisible(true); 1712 } 1713 }); 1714 } 1715 }); 1716 btns.add(new JButton("Break Lines in Selection") { 1717 final static long serialVersionUID = 42; 1718 1719 { 1720 addActionListener(new ActionListener() { 1721 public void actionPerformed(ActionEvent e) { 1722 commandWindow.breakLines(70); 1723 } 1724 }); 1725 } 1726 }); 1727 btns.add(new JButton("Print") { 1728 final static long serialVersionUID = 42; 1729 1730 { 1731 addActionListener(new ActionListener() { 1732 public void actionPerformed(ActionEvent e) { 1733 commandWindow.print("Command Window"); 1734 } 1735 }); 1736 } 1737 }); 1738 btns.add(new JButton("Read File") { 1739 final static long serialVersionUID = 42; 1740 1741 { 1742 addActionListener(new ActionListener() { 1743 public void actionPerformed(ActionEvent e) { 1744 File file = chooseFile("Read", 1745 new String[][]{{"*.txt", "txt"}}); 1746 if(file != null) { 1747 editFile = file; 1748 String d = file.getParent(); 1749 if(d != null) currentDirectory = d; 1750 String s = readFileIntoString(file); 1751 if(s != null) { 1752 textArea.setText(s); 1753 textArea.setCaretPosition(0); 1754 textArea.grabFocus(); 1755 commandWindow.setTitle("SQL Command Window - " + file); 1756 } 1757 } 1758 textArea.setCaretPosition(0); 1759 textArea.grabFocus(); 1760 } 1761 }); 1762 } 1763 }); 1764 btns.add(new JButton("Write File") { 1765 final static long serialVersionUID = 42; 1766 1767 { 1768 addActionListener(new ActionListener() { 1769 public void actionPerformed(ActionEvent e) { 1770 File file = chooseFile("Write", 1771 new String[][]{{"*.txt", "txt"}}); 1772 if(file != null) { 1773 editFile = file; 1774 String d = file.getParent(); 1775 if(d != null) currentDirectory = d; 1776 OutputStream out = null; 1777 try { 1778 out = new FileOutputStream(editFile); 1779 out.write(textArea.getText().getBytes(fileCharset)); 1780 } catch(IOException ex) { 1781 ex.printStackTrace(); 1782 } finally { 1783 close(out); 1784 } 1785 } 1786 textArea.grabFocus(); 1787 } 1788 }); 1789 } 1790 }); 1791 inner.add(btns, BorderLayout.NORTH); 1792 inner.add(status, BorderLayout.SOUTH); 1793 commandWindow.getContentPane().add(inner, BorderLayout.SOUTH); 1794 commandWindow.setLocationByPlatform(true); 1795 commandWindow.pack(); 1796 commandWindow.setVisible(true); 1797 } 1798 1799 /** 1800 * This class provides a search and replace function for table 1801 * entries. It can do global replaces or confine its activities to a 1802 * single field, table, or entry. 1803 * 1804 * Layout: 1805 * 1806 * (((Table |XXX| Field |XXX| _rowid_ |XXX|))) 1807 * 1808 * | Find | |XXXXX| | Replace | |XXXXX| 1809 * |Find Funny| |XXXXX| |Replace/Find| |XXXXX| 1810 * 1811 * Find: select next occurance after current selection 1812 * Find Funny: find the next "funny" character 1813 * Replace: replace current selection with replacement string 1814 * Replace/Find: do a Replace followed by a Find 1815 */ 1816 class FindReplace extends JFrame { 1817 final static long serialVersionUID = 42; 1818 1819 JTextArea textArea; 1820 JPanel statusLine = new JPanel(new BorderLayout()); 1821 JLabel status = new JLabel("Status area"); 1822 JTextArea findField = new UndoableTextArea(4, 15); 1823 JTextArea replaceField = new UndoableTextArea(4, 15); 1824 /** 1825 * Make a find/replace window operating on the given textArea. 1826 * 1827 * @param editWindow the EditWindow to operate on 1828 * @param ignoreCase true if case is ignored in comparisons 1829 */ 1830 FindReplace(EditWindow editWindow, boolean ignoreCase) { 1831 this.textArea = editWindow.textArea; 1832 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // currently no dependents to close 1833 Rectangle r = editWindow.getBounds(); 1834 setLocation(r.x + r.width, r.y); 1835 setTitle("Find - Replace"); 1836 JPanel args2 = new JPanel(); 1837 JPanel btns1 = new JPanel(new GridLayout(2, 1)); 1838 JCheckBox checkBox = new JCheckBox("Ignore Case", ignoreCase); 1839 btns1.add(new JButton("Find:") { 1840 final static long serialVersionUID = 42; 1841 1842 { 1843 addActionListener(new ActionListener() { 1844 public void actionPerformed(ActionEvent e) { 1845 status.setText(""); 1846 String text = textArea.getText(); 1847 String find = findField.getText(); 1848 int end = textArea.getSelectionEnd(); 1849 int location = findIgnoreCase(checkBox.isSelected(), text, find, end); 1850 if(location < 0) { 1851 status.setText("not found"); 1852 textArea.select(0, 0); 1853 } else { 1854 textArea.select(location, location + find.length()); 1855 editWindow.toFront(); 1856 } 1857 } 1858 }); 1859 } 1860 }); 1861 btns1.add(new JButton("Find Funny") { 1862 final static long serialVersionUID = 42; 1863 1864 { 1865 addActionListener(new ActionListener() { 1866 public void actionPerformed(ActionEvent e) { 1867 status.setText(""); 1868 String text = textArea.getText(); 1869 int end = textArea.getSelectionEnd(); 1870 for(int i = end; i < text.length(); i++) { 1871 char c = text.charAt(i); 1872 if(c != 0xa && (c < 32 || c > 126)) { 1873 status.setText("Hex Character = " 1874 + Integer.toHexString(c)); 1875 textArea.select(i, i + 1); 1876 editWindow.toFront(); 1877 return; 1878 } 1879 } 1880 status.setText("not found"); 1881 textArea.select(0, 0); 1882 } 1883 }); 1884 } 1885 }); 1886 args2.add(btns1); 1887 JScrollPane findScrollPane = new JScrollPane(findField); 1888 args2.add(findScrollPane); 1889 args2.add(new JLabel(" ")); 1890 JPanel btns2 = new JPanel(new GridLayout(2, 1)); 1891 btns2.add(new JButton("Replace:") { 1892 final static long serialVersionUID = 42; 1893 1894 { 1895 addActionListener(new ActionListener() { 1896 public void actionPerformed(ActionEvent e) { 1897 status.setText(""); 1898 String replace = replaceField.getText(); 1899 textArea.replaceSelection(replace); 1900 int end = textArea.getSelectionEnd(); 1901 textArea.select(end - replace.length(), end); 1902 } 1903 }); 1904 } 1905 }); 1906 args2.add(btns2); 1907 btns2.add(new JButton("Replace/Find:") { 1908 final static long serialVersionUID = 42; 1909 1910 { 1911 addActionListener(new ActionListener() { 1912 public void actionPerformed(ActionEvent e) { 1913 status.setText(""); 1914 String replace = replaceField.getText(); 1915 textArea.replaceSelection(replace); 1916 int end = textArea.getSelectionEnd(); 1917 textArea.select(end - replace.length(), end); 1918 String text = textArea.getText(); 1919 String find = findField.getText(); 1920 int location = findIgnoreCase(checkBox.isSelected(), text, find, end); 1921 if(location < 0) { 1922 status.setText("not found"); 1923 textArea.select(0, 0); 1924 } else { 1925 textArea.select(location, location + find.length()); 1926 editWindow.toFront(); 1927 } 1928 } 1929 }); 1930 } 1931 }); 1932 args2.add(btns2); 1933 JScrollPane replaceScrollPane = new JScrollPane(replaceField); 1934 args2.add(replaceScrollPane); 1935 getContentPane().add(args2, BorderLayout.CENTER); 1936 statusLine.add(status, BorderLayout.CENTER); 1937 statusLine.add(checkBox, BorderLayout.EAST); 1938 getContentPane().add(statusLine, BorderLayout.SOUTH); 1939 } 1940 } // class FindReplace extends JFrame 1941 1942 /** 1943 * This class searches many cells in a table column. 1944 * This class provides a search and replace function for table 1945 * entries. It can do global replaces or confine its activities to a 1946 * single field, table, or entry. 1947 * 1948 * Layout: 1949 * 1950 * Table |XXX| Field |XXX| 1951 * 1952 * | Find | |XXXXX| | To Hex | |XXXXX| 1953 * | | |XXXXX| | | |XXXXX| 1954 * |Find Funny| |XXXXX| |From Hex| |XXXXX| 1955 * 1956 * Find: select next occurance after current selection 1957 * Find Funny: find the next "funny" character 1958 * To Hex: replace contents with hex representation 1959 * Replace/Find: replace hex representation with text 1960 * 1961 * Current state is [table, row, column, textArea position] 1962 * or 1963 * [table selection, textArea position] 1964 * 1965 * Maybe just bring up table, selection, edit window on found instances 1966 */ 1967 class Search extends JFrame { 1968 final static long serialVersionUID = 42; 1969 1970 Table table; // the table to search 1971 JTextField tableField = new JTextField(10); 1972 DefaultComboBoxModel<String> model = new DefaultComboBoxModel<String>(); 1973 JComboBox<String> columnField = new JComboBox<String>(model); 1974 JCheckBox checkBox = new JCheckBox("Ignore Case", true); 1975 JTextArea findField = new UndoableTextArea(4, 15); 1976 JTextArea replaceField = new UndoableTextArea(4, 15); 1977 JLabel status = new JLabel("Status area"); 1978 1979 /** 1980 * Make a find/replace window operating on the given textArea. 1981 * 1982 * @param table the table to operate on 1983 */ 1984 Search(Table table) { 1985 this.table = table; 1986 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // currently no dependents 1987 setTitle("Searching table: " + table.getTitle()); 1988 JPanel args1 = new JPanel(); 1989 args1.add(new JLabel("Table:")); 1990 args1.add(tableField); 1991 tableField.setEditable(false); 1992 args1.add(new JLabel(" Column:")); 1993 args1.add(columnField); 1994 for(int i = 0; i < table.heading.size(); i++) { 1995 model.addElement(table.heading.get(i)); 1996 } 1997 args1.add(checkBox); 1998 getContentPane().add(args1, BorderLayout.NORTH); 1999 JPanel args2 = new JPanel(); 2000 JPanel btns1 = new JPanel(new GridLayout(2, 1)); 2001 btns1.add(new JButton("Find:") { 2002 final static long serialVersionUID = 42; 2003 2004 { 2005 addActionListener(new ActionListener() { 2006 public void actionPerformed(ActionEvent e) { 2007 Table table = Search.this.table; 2008 status.setText(""); 2009 String find = findField.getText(); 2010 String replace = replaceField.getText(); 2011 int column 2012 = table.heading.indexOf(columnField.getSelectedItem()); 2013 if(column < 0 || column >= table.heading.size()) { 2014 status.setText("Illegal column specification"); 2015 return; // illegal column 2016 } 2017 boolean ignoreCase = checkBox.isSelected(); 2018 for(int row = table.table.getSelectedRow() + 1; 2019 row < table.data.size(); 2020 ++row) { 2021 String cell = table.data.get(row).get(column); 2022 String value = table.data.get(row).get(column); 2023 if(findIgnoreCase(ignoreCase, value, find, 0) >= 0) { 2024 int col = table.table.convertColumnIndexToView(column); 2025 Rectangle r = Search.this.getBounds(); 2026 // bring up an editor window 2027 EditWindow editWindow = table.makeCellEditWindow(table, row, col, r); 2028 FindReplace findReplace = new FindReplace(editWindow, ignoreCase); 2029 editWindow.dependents.add(findReplace); 2030 findReplace.findField.setText(find); 2031 findReplace.replaceField.setText(replace); 2032 findReplace.pack(); 2033 findReplace.setVisible(true); 2034 return; 2035 } 2036 } 2037 status.setText("not found"); 2038 table.table.clearSelection(); 2039 } 2040 }); 2041 } 2042 }); 2043 btns1.add(new JButton("Find Funny") { 2044 final static long serialVersionUID = 42; 2045 2046 { 2047 addActionListener(new ActionListener() { 2048 public void actionPerformed(ActionEvent e) { 2049 Table table = Search.this.table; 2050 status.setText(""); 2051 String find = findField.getText(); 2052 String replace = replaceField.getText(); 2053 int column 2054 = table.heading.indexOf(columnField.getSelectedItem()); 2055 if(column < 0 || column >= table.heading.size()) { 2056 status.setText("Illegal column specification"); 2057 return; // illegal column 2058 } 2059 for(int row = table.table.getSelectedRow() + 1; 2060 row < table.data.size(); 2061 ++row) { 2062 String cell = table.data.get(row).get(column); 2063 int location = -1; // initialize to not found 2064 String value = table.data.get(row).get(column); 2065 if(value != null) { 2066 for(int i = 0; i < value.length(); i++) { 2067 char c = value.charAt(i); 2068 if(c != 0xa && (c < 32 || c > 126)) { 2069 status.setText("Hex Character = " 2070 + Integer.toHexString(c)); 2071 location = 0; 2072 break; 2073 } 2074 } 2075 } 2076 if(location >= 0) { 2077 int col = table.table.convertColumnIndexToView(column); 2078 // bring up an editor window 2079 Rectangle r = Search.this.getBounds(); 2080 EditWindow editWindow = table.makeCellEditWindow(table, row, col, r); 2081 FindReplace findReplace = new FindReplace(editWindow, true); 2082 editWindow.dependents.add(findReplace); 2083 findReplace.findField.setText(find); 2084 findReplace.replaceField.setText(replace); 2085 findReplace.pack(); 2086 findReplace.setVisible(true); 2087 return; 2088 } 2089 } 2090 status.setText("not found"); 2091 table.table.clearSelection(); 2092 } 2093 }); 2094 } 2095 }); 2096 args2.add(btns1); 2097 JScrollPane findScrollPane = new JScrollPane(findField); 2098 args2.add(findScrollPane); 2099 args2.add(new JLabel(" ")); 2100 JPanel btns2 = new JPanel(new GridLayout(2, 1)); 2101 btns2.add(new JButton("To Hex:") { 2102 final static long serialVersionUID = 42; 2103 2104 { 2105 addActionListener(new ActionListener() { 2106 public void actionPerformed(ActionEvent e) { 2107 String replace = replaceField.getText(); 2108 StringBuilder hex = new StringBuilder(); 2109 for(int i = 0; i < replace.length(); i++) { 2110 if(hex.length() != 0) hex.append('|'); 2111 hex.append(Integer.toHexString(replace.charAt(i))); 2112 } 2113 replaceField.setText(hex.toString()); 2114 } 2115 }); 2116 } 2117 }); 2118 args2.add(btns2); 2119 btns2.add(new JButton("From Hex:") { 2120 final static long serialVersionUID = 42; 2121 2122 { 2123 addActionListener(new ActionListener() { 2124 public void actionPerformed(ActionEvent e) { 2125 String replace = replaceField.getText(); 2126 StringBuilder hex = new StringBuilder(); 2127 int charValue = 0; 2128 boolean haveValue = false; 2129 for(int i = 0; i < replace.length(); i++) { 2130 char c = replace.charAt(i); 2131 if(c >= '0' && c <= '9') { 2132 haveValue = true; 2133 charValue = 16*charValue + c - '0'; 2134 } else if(c >= 'A' && c <= 'F') { 2135 haveValue = true; 2136 charValue = 16*charValue + c - 'A' + 10; 2137 } else if(c >= 'a' && c <= 'f') { 2138 haveValue = true; 2139 charValue = 16*charValue + c - 'a' + 10; 2140 } else { 2141 if(haveValue) { 2142 hex.append((char)charValue); 2143 charValue = 0; 2144 haveValue = false; 2145 } 2146 } 2147 } 2148 if(haveValue) { 2149 hex.append((char)charValue); 2150 haveValue = false; 2151 } 2152 replaceField.setText(hex.toString()); 2153 } 2154 }); 2155 } 2156 }); 2157 args2.add(btns2); 2158 JScrollPane replaceScrollPane = new JScrollPane(replaceField); 2159 args2.add(replaceScrollPane); 2160 getContentPane().add(args2, BorderLayout.CENTER); 2161 getContentPane().add(status, BorderLayout.SOUTH); 2162 } 2163 } // class Search extends JFrame 2164 2165 /** 2166 * This class is identical to a JTextArea but also has undo / redo 2167 * capability. 2168 * 2169 * cntrl-Z is undo 2170 * cntrl-Y is redo 2171 */ 2172 class UndoableTextArea extends JTextArea { 2173 final static long serialVersionUID = 42; 2174 2175 UndoableTextArea() { 2176 super(); 2177 } 2178 2179 UndoableTextArea(Document doc) { 2180 super(doc); 2181 } 2182 2183 UndoableTextArea(Document doc, String text, int rows, int columns) { 2184 super(doc, text, rows, columns); 2185 } 2186 2187 UndoableTextArea(int rows, int columns) { 2188 super(rows, columns); 2189 } 2190 2191 UndoableTextArea(String text) { 2192 super(text); 2193 } 2194 2195 UndoableTextArea(String text, int rows, int columns) { 2196 super(text, rows, columns); 2197 } 2198 2199 { 2200 UndoManager undoManager = new UndoManager(); 2201 undoManager.setLimit(-1); // unlimited undos 2202 Document doc = getDocument(); 2203 doc.addUndoableEditListener(new UndoableEditListener() { 2204 //@Override 2205 public void undoableEditHappened(UndoableEditEvent e) { 2206 undoManager.addEdit(e.getEdit()); 2207 } 2208 }); 2209 2210 InputMap im = getInputMap(JComponent.WHEN_FOCUSED); 2211 ActionMap am = getActionMap(); 2212 2213 Toolkit toolkit = Toolkit.getDefaultToolkit(); 2214 2215 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, 2216 toolkit.getMenuShortcutKeyMask()), 2217 "Undo"); 2218 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, 2219 toolkit.getMenuShortcutKeyMask()), 2220 "Redo"); 2221 2222 am.put("Undo", new AbstractAction() { 2223 final static long serialVersionUID = 42; 2224 //@Override 2225 public void actionPerformed(ActionEvent e) { 2226 try { 2227 if (undoManager.canUndo()) { 2228 undoManager.undo(); 2229 } 2230 } catch (CannotUndoException exp) { 2231 exp.printStackTrace(); 2232 } 2233 } 2234 }); 2235 am.put("Redo", new AbstractAction() { 2236 final static long serialVersionUID = 42; 2237 //@Override 2238 public void actionPerformed(ActionEvent e) { 2239 try { 2240 if (undoManager.canRedo()) { 2241 undoManager.redo(); 2242 } 2243 } catch (CannotUndoException exp) { 2244 exp.printStackTrace(); 2245 } 2246 } 2247 }); 2248 } 2249 } // class UndoableTextArea extends JTextArea 2250 2251 // Here are classes and methods used to dump the database. Note that 2252 // the method println is not used as this method outputs a 2253 // platform-dependent end-of-line character. 2254 2255 /** 2256 * Abstract class to operate on rows of a table. 2257 */ 2258 abstract static class RowProc { 2259 /** 2260 * Method called on each row of a table resulting from a query. 2261 * 2262 * @param row the row of the table resulting from a query 2263 */ 2264 abstract void doRow(ArrayList<String> row); 2265 } // abstract class RowProc 2266 2267 /** 2268 * Queries a database and calls a method on each row of the result set. 2269 * 2270 * @param query the query (select statement) to execute 2271 * @param rowProc a class encapsulating the method to run on each row 2272 */ 2273 void queryTable(String query, RowProc rowProc) { 2274 ArrayList<String> row = new ArrayList<String>(); 2275 Statement stat = null; 2276 try { 2277 stat = conn.createStatement(); 2278 stat.execute(query); 2279 int count = stat.getUpdateCount(); 2280 if(count >= 0) { 2281 System.err.println("No table result"); 2282 System.err.println(query); 2283 return; 2284 } 2285 ResultSet rs = stat.getResultSet(); 2286 ResultSetMetaData md = rs.getMetaData(); 2287 int tablecolumns = md.getColumnCount(); 2288 ArrayList<Integer> columnIndex = new ArrayList<Integer>(); 2289 while(rs.next()) { 2290 ArrayList<String> data = new ArrayList<String>(); 2291 for(int i = 1; i <= tablecolumns; i++) { 2292 data.add(getWithCharset(rs, i)); 2293 } 2294 rowProc.doRow(data); 2295 } 2296 } catch(SQLException e) { 2297 System.err.println("SQL problem in queryTable function:"); 2298 System.err.println(query); 2299 e.printStackTrace(); 2300 return; 2301 //throw e; 2302 } finally { 2303 try { 2304 if(stat != null) stat.close(); 2305 } catch(SQLException e) { 2306 e.printStackTrace(); 2307 } 2308 } 2309 } 2310 2311 /** 2312 * Run query and output result followed by a ';' for each 2313 * row. Assume only one column in the result. 2314 * 2315 * @param out the OutputStream to be used for output 2316 * @param query the query string 2317 */ 2318 void runTableDumpQuery(OutputStream out, String query) { 2319 queryTable(query, new RowProc() { 2320 void doRow(ArrayList<String> row) { 2321 try { 2322 if(row.size() != 1) System.err.println("### error number of columns in runTableDumpQuery"); 2323 String s = row.get(0); 2324 if (s == null) out.write("null".getBytes(fileCharset)); 2325 else out.write(s.getBytes(fileCharset)); 2326 if(s.indexOf("--") >= 0) out.write("\n;\n".getBytes(fileCharset)); 2327 else out.write(";\n".getBytes(fileCharset)); 2328 } catch(IOException e) { 2329 e.printStackTrace(); 2330 } 2331 } 2332 }); 2333 } 2334 2335 /** 2336 * Dump table etc. in the main database. 2337 * 2338 * @param out the OutputStream to be used for output 2339 * @param query the query string 2340 */ 2341 void runSchemaDumpQuery(OutputStream out, String query) { 2342 queryTable(query, new RowProc() { 2343 void doRow(ArrayList<String> row) { 2344 try { 2345 String table = row.get(0); 2346 String type = row.get(1); 2347 String sql = row.get(2); 2348 if("sqlite_sequence".equals(table)) { 2349 out.write("DELETE FROM sqlite_sequence;\n".getBytes(fileCharset)); 2350 } else if(table.length() == 12 && table.substring(0,11).equals("sqlite_stat")) { 2351 out.write("ANALYZE sqlite_master;\n".getBytes(fileCharset)); 2352 } else if(table.length() >= 7 && table.substring(0,7).equals("sqlite_")) { 2353 return; 2354 } else if(table.length() >= 20 && table.substring(0,20).equals("CREATE VIRTUAL TABLE")) { 2355 // not handled 2356 return; 2357 } else { 2358 out.write((sql + ";\n").getBytes(fileCharset)); 2359 } 2360 if(type.equals("table")) { 2361 StringBuilder names = new StringBuilder(); 2362 queryTable("PRAGMA table_info(" + escapeString(table, '"') + ");", 2363 new RowProc() { 2364 void doRow(ArrayList<String> row) { 2365 if(names.length() != 0) names.append("||','||"); 2366 names.append("quote("); 2367 names.append(escapeString(row.get(1), '"')); 2368 names.append(")"); 2369 } 2370 }); 2371 String query = "SELECT 'INSERT INTO ' || " 2372 + escapeString(table, '"') 2373 + " || ' VALUES(' || " 2374 + names 2375 + "|| ')' FROM " 2376 + escapeString(table, '"'); 2377 runTableDumpQuery(out, query); 2378 } 2379 } catch(IOException e) { 2380 e.printStackTrace(); 2381 } 2382 return; 2383 } 2384 }); 2385 } 2386 2387 File backupFile = new File(".", "database.txt"); // default backup and restore text file 2388 2389 /** 2390 * Dump the main database 2391 * 2392 * @param out the OutputStream to be used for output 2393 */ 2394 void dumpDatabase(OutputStream out) { 2395 try{ 2396 out.write("PRAGMA foreign_keys=OFF;\n".getBytes(fileCharset)); 2397 out.write("BEGIN TRANSACTION;\n".getBytes(fileCharset)); 2398 // "SAVEPOINT dump; PRAGMA writable_schema=ON" 2399 runSchemaDumpQuery(out, 2400 "SELECT name, type, sql FROM sqlite_master " + 2401 "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'"); 2402 runSchemaDumpQuery(out, 2403 "SELECT name, type, sql FROM sqlite_master " + 2404 "WHERE name=='sqlite_sequence'"); 2405 runTableDumpQuery(out, 2406 "SELECT sql FROM sqlite_master " + 2407 "WHERE sql NOT NULL AND type IN ('index','trigger','view')"); 2408 out.write("COMMIT;\n".getBytes(fileCharset)); 2409 } catch(IOException e) { 2410 e.printStackTrace(); 2411 } 2412 } 2413 2414 static boolean IdChar(char c) { 2415 if('A' <= c && c <= 'Z') return true; 2416 if('a' <= c && c <= 'z') return true; 2417 if('0' <= c && c <= '9') return true; 2418 if(c == '_') return true; 2419 return false; 2420 } 2421 2422 /** 2423 * Returns the line number of a character position in a string 2424 * 2425 * @param s the string to search 2426 * @param charPos the position in the string 2427 * @return the line number (one-based) of the character position 2428 */ 2429 static int lineNumber(String s, int charPos) { 2430 int count = 0; 2431 for(int i = 0; i < charPos; i++) { 2432 if(s.charAt(i) == '\n') count++; 2433 } 2434 return count; 2435 } 2436 2437 /** 2438 * Check to see if the String query is at position in String s. This comparison ignores case. 2439 * 2440 * @param s the base String to check 2441 * @param position character position in s to check 2442 * @param query the string to match 2443 * @return true if String query is in s at position 2444 */ 2445 static boolean stringCompare(String s, int position, String query) { 2446 if(s == null) return false; // may not be needed 2447 int sLength = s.length(); 2448 int qLength = query.length(); 2449 if(position + qLength > sLength) return false; 2450 return s.substring(position, position + qLength).compareToIgnoreCase(query) == 0; 2451 } 2452 2453 // State machine for partially parsing SQL statements. 2454 static final int trans[][] = new int[][]{ 2455 /* Token: */ 2456 /* State: ** SEMI WS OTHER EXPLAIN CREATE TEMP TRIGGER END */ 2457 /* 0 INVALID: */ { 1, 0, 2, 3, 4, 2, 2, 2, }, 2458 /* 1 START: */ { 1, 1, 2, 3, 4, 2, 2, 2, }, 2459 /* 2 NORMAL: */ { 1, 2, 2, 2, 2, 2, 2, 2, }, 2460 /* 3 EXPLAIN: */ { 1, 3, 3, 2, 4, 2, 2, 2, }, 2461 /* 4 CREATE: */ { 1, 4, 2, 2, 2, 4, 5, 2, }, 2462 /* 5 TRIGGER: */ { 6, 5, 5, 5, 5, 5, 5, 5, }, 2463 /* 6 SEMI: */ { 6, 6, 5, 5, 5, 5, 5, 7, }, 2464 /* 7 END: */ { 1, 7, 5, 5, 5, 5, 5, 5, }, 2465 }; 2466 2467 /** 2468 * Find the end of an SQL staement starting at character position 2469 * start. Only works for valid SQL statements. Note that the 2470 * discovered SQL statement may not be valid for illegal SQL 2471 * statements. The complexity is because of the syntax of CREATE 2472 * TRIGGER statements. This code was taken from the sqlite database 2473 * C code. 2474 * 2475 * This implementation uses a state machine with 8 states: 2476 * 2477 * (0) INVALID We have not yet seen a non-whitespace character. 2478 * 2479 * (1) START At the beginning or end of an SQL statement. This 2480 * routine returns if it ends in the START state or reaches the 2481 * end of the string. 2482 * 2483 * (2) NORMAL We are in the middle of statement which ends with a 2484 * single semicolon. 2485 * 2486 * (3) EXPLAIN The keyword EXPLAIN has been seen at the beginning 2487 * of a statement. 2488 * 2489 * (4) CREATE The keyword CREATE has been seen at the beginning of 2490 * a statement, possibly preceded by EXPLAIN and/or followed by 2491 * TEMP or TEMPORARY 2492 * 2493 * (5) TRIGGER We are in the middle of a trigger definition that 2494 * must be ended by a semicolon, the keyword END, and another 2495 * semicolon. 2496 * 2497 * (6) SEMI We've seen the first semicolon in the ";END;" that 2498 * occurs at the end of a trigger definition. 2499 * 2500 * (7) END We've seen the ";END" of the ";END;" that occurs at the 2501 * end of a trigger definition. 2502 * 2503 * @param s the String of SQL statements 2504 * @param start the character position of the beginning of the SQL statement 2505 * @return the character position one beyond the end of the discovered statement 2506 */ 2507 int getEndOfSqlStatement(String s, int start) { 2508 final int tkSEMI = 0; // A semicolon. 2509 final int tkWS = 1; // Whitespace. 2510 final int tkOTHER = 2; // Any other SQL token. 2511 final int tkEXPLAIN = 3; // The "explain" keyword. 2512 final int tkCREATE = 4; // The "create" keyword. 2513 final int tkTEMP = 5; // The "temp" or "temporary" keyword. 2514 final int tkTRIGGER = 6; // The "trigger" keyword. 2515 final int tkEND = 7; // The "end" keyword. 2516 2517 int sLength = s.length(); 2518 int state = 0; /* Current state, using numbers defined in header comment */ 2519 int token; /* Value of the next token */ 2520 int p = start; 2521 while(state != 1 && p < sLength) { 2522 loop: switch( s.charAt(p) ) { 2523 case ';': { /* A semicolon */ 2524 token = tkSEMI; 2525 break; 2526 } 2527 case ' ': 2528 case '\r': 2529 case '\t': 2530 case '\n': 2531 case '\f': { /* White space is ignored */ 2532 token = tkWS; 2533 break; 2534 } 2535 case '/': { /* C-style comments */ 2536 if( p + 1 >= sLength || s.charAt( p + 1 ) !='*' ) { 2537 token = tkOTHER; 2538 break loop; 2539 } 2540 p += 2; 2541 while( p < sLength && !stringCompare(s, p, "*/") ) p++; 2542 if(p < sLength) p++; 2543 token = tkWS; 2544 break loop; 2545 } 2546 case '-': { /* SQL-style comments from "--" to end of line */ 2547 if( p + 1 < sLength && s.charAt(p + 1)!='-' ) { 2548 token = tkOTHER; 2549 break loop; 2550 } 2551 while( p < sLength && s.charAt(p) != '\n' ) { p++; } 2552 token = tkWS; 2553 break loop; 2554 } 2555 case '[': { /* Microsoft-style identifiers in [...] */ 2556 p++; 2557 while( p < sLength && s.charAt(p) != ']' ) { p++; } 2558 token = tkOTHER; 2559 break loop; 2560 } 2561 case '`': /* Grave-accent quoted symbols used by MySQL */ 2562 case '"': /* single- and double-quoted strings */ 2563 case '\'': { 2564 char c = s.charAt(p); 2565 p++; 2566 while( p < sLength && s.charAt(p)!=c ) { p++; } 2567 token = tkOTHER; 2568 break loop; 2569 } 2570 default: { 2571 if( IdChar(s.charAt(p)) ) { 2572 /* Keywords and unquoted identifiers */ 2573 int nId; 2574 for(nId=1; 2575 p + nId < sLength && IdChar(s.charAt(p + nId)); 2576 nId++) {} 2577 inner: switch( s.charAt(p) ) { 2578 case 'c': case 'C': { 2579 if( nId==6 && stringCompare(s, p, "create") ) { 2580 token = tkCREATE; 2581 }else{ 2582 token = tkOTHER; 2583 } 2584 break inner; 2585 } 2586 case 't': case 'T': { 2587 if( nId==7 && stringCompare(s, p, "trigger") ) { 2588 token = tkTRIGGER; 2589 }else if( nId==4 && stringCompare(s, p, "temp") ) { 2590 token = tkTEMP; 2591 }else if( nId==9 && stringCompare(s, p, "temporary") ) { 2592 token = tkTEMP; 2593 }else{ 2594 token = tkOTHER; 2595 } 2596 break inner; 2597 } 2598 case 'e': case 'E': { 2599 if( nId==3 && stringCompare(s, p, "end") ) { 2600 token = tkEND; 2601 }else 2602 if( nId==7 && stringCompare(s, p, "explain") ) { 2603 token = tkEXPLAIN; 2604 }else { 2605 token = tkOTHER; 2606 } 2607 break inner; 2608 } 2609 default: { 2610 token = tkOTHER; 2611 break inner; 2612 } 2613 } 2614 p += nId-1; 2615 }else{ 2616 /* Operators and special symbols */ 2617 token = tkOTHER; 2618 } 2619 break loop; 2620 } 2621 } 2622 state = trans[state][token]; 2623 p++; 2624 } 2625 return p; 2626 } 2627 2628 static String currentDirectory = "."; 2629 2630 /** 2631 * Bring up a file browser to choose a file. 2632 * 2633 * Second argument is a String[][] where each array is {desc, ext, ext, ext}. 2634 * this matches the arguments to Class FileNameExtensionFilter 2635 * 2636 * @param approve the label in the approve button 2637 * @param filters a description of the filters to be used 2638 * @return the name of the chosen file or null if no selection 2639 */ 2640 static File chooseFile(String approve, String[]...filters) { 2641 File file; 2642 JFileChooser fc = new JFileChooser(currentDirectory); 2643 fc.setFileHidingEnabled(false); 2644 fc.setApproveButtonText(approve); 2645 for(String[] filter : filters) { 2646 String[] extensions = Arrays.copyOfRange(filter, 1, filter.length); 2647 fc.addChoosableFileFilter(new FileNameExtensionFilter(filter[0], 2648 extensions)); 2649 } 2650 int status = fc.showDialog(null, approve); 2651 if (status == JFileChooser.APPROVE_OPTION) { 2652 file = fc.getSelectedFile(); 2653 } else { 2654 file = null; 2655 } 2656 if(file != null) { 2657 String d = file.getParent(); 2658 if(d != null) currentDirectory = d; 2659 } 2660 return file; 2661 } 2662 2663 /** 2664 * This class is a pure data structure. 2665 */ 2666 static class DatabaseTableEntry implements Comparable<DatabaseTableEntry> { 2667 String dbName; 2668 String tableName; 2669 String type; 2670 boolean ambiguous = false; 2671 2672 /** 2673 * Create a DatabaseTableEntry. 2674 * 2675 * @param dbName the database name 2676 * @param tableName the name of the table in the database 2677 * @param type table or view or index or trigger etc. 2678 */ 2679 DatabaseTableEntry(String dbName, String tableName, String type) { 2680 this.dbName = dbName; 2681 this.tableName = tableName; 2682 this.type = type; 2683 } 2684 2685 public int compareTo(DatabaseTableEntry other) { 2686 int c = tableName.compareTo(other.tableName); 2687 if(c != 0) return c; 2688 ambiguous = other.ambiguous = true; 2689 c = dbName.compareTo(other.dbName); 2690 return c; 2691 } 2692 2693 String fullName() { 2694 return dbName + '.' + tableName; 2695 } 2696 2697 public String toString() { 2698 if(!ambiguous) return tableName; 2699 return dbName + '.' + tableName; 2700 } 2701 } // static class DatabaseTableEntry implements Comparable<DatabaseTableEntry> 2702 2703 ArrayList<DatabaseTableEntry> tables; 2704 JList<Object> list; 2705 boolean hidden; 2706 2707 /** 2708 * Bring up a database editor 2709 * 2710 * @param db the name of the database to display 2711 */ 2712 static void makeTableList(String db) { 2713 if(db == null) { 2714 File file = chooseFile("Open", 2715 new String[][]{{"*.db *.sqlite", "db", "sqlite"}}); 2716 if(file == null) return; 2717 db = file.getPath(); 2718 } 2719 if(db == null) return; 2720 try { 2721 Connection conn = DriverManager.getConnection("jdbc:sqlite:" + db); 2722 DatabaseEditor frame = new DatabaseEditor(conn, db, false); 2723 frame.pack(); 2724 frame.setVisible(true); 2725 } catch(SQLException ex) { 2726 ex.printStackTrace(); 2727 } 2728 } 2729 2730 /** 2731 * Create a database editor 2732 * 2733 * @param conn the database connection 2734 * @param db the name of the database 2735 * @param hidden true if showing hidden tables (always called with false) 2736 */ 2737 DatabaseEditor(Connection conn, String db, boolean hidden) { 2738 this.hidden = hidden; 2739 this.conn = conn; 2740 addWindowListener(new WindowAdapter() { 2741 public void windowClosing(WindowEvent e) {DatabaseEditor.this.dispose(); } 2742 public void windowClosed(WindowEvent e) { 2743 for(JFrame d : dependents) d.dispose(); 2744 try { 2745 conn.close(); 2746 } catch(SQLException ex) { 2747 System.err.println("Exception on closing top window"); 2748 ex.printStackTrace(); ///////////// ??????????? 2749 } 2750 } 2751 }); 2752 setLocationByPlatform(true); 2753 setTitle(db); 2754 initList(hidden); 2755 list = new JList<Object>(tables.toArray()) { 2756 final static long serialVersionUID = 42; 2757 2758 public String getToolTipText(MouseEvent e) { 2759 int index = locationToIndex(e.getPoint()); 2760 if (index > -1) { 2761 DatabaseTableEntry item 2762 = (DatabaseTableEntry)getModel().getElementAt(index); 2763 return item.dbName + '.' + item.tableName; 2764 } 2765 return null; 2766 } 2767 }; 2768 list.setCellRenderer(new DefaultListCellRenderer() { 2769 final static long serialVersionUID = 42; 2770 2771 public Component getListCellRendererComponent(JList<?> list, 2772 Object value, 2773 int index, 2774 boolean isSelected, 2775 boolean cellHasFocus) { 2776 DatabaseTableEntry entry = (DatabaseTableEntry)value; 2777 JLabel label = new JLabel(entry.toString()); 2778 label.setOpaque(true); 2779 // 0xffffc0 is light yellow 2780 if(entry.type.equals("view")) label.setBackground(new Color(0xffffc0)); 2781 return label; 2782 } 2783 }); 2784 JScrollPane scrollPane = new JScrollPane(list); 2785 scrollPane.setPreferredSize(new Dimension(200, 600)); 2786 getContentPane().add(scrollPane); 2787 list.addMouseListener(new MouseAdapter() { 2788 public void mouseClicked(MouseEvent e) { 2789 if(e.getButton() == 1) { 2790 int i = list.locationToIndex(e.getPoint()); 2791 if(i >= 0) { 2792 showTable(tables.get(i).fullName()); 2793 } 2794 } else if(e.getButton() == 3) { 2795 int i = list.locationToIndex(e.getPoint()); 2796 if(i >= 0) { 2797 String tableName = tables.get(i).toString(); 2798 Table table = new Table(tableName); 2799 table.pack(); 2800 table.setVisible(true); 2801 Search search = new Search(table); 2802 table.dependents.add(search); 2803 Rectangle r = table.getBounds(); 2804 search.setLocation(r.x + r.width, r.y); 2805 search.tableField.setText(tableName); 2806 search.pack(); 2807 search.setVisible(true); 2808 } 2809 } 2810 } 2811 }); 2812 2813 JPanel buttons = new JPanel(new GridLayout(6, 1)); 2814 2815 buttons.add(new JButton("Refresh") { 2816 final static long serialVersionUID = 42; 2817 2818 { 2819 addActionListener(new ActionListener() { 2820 public void actionPerformed(ActionEvent e) { 2821 initList(DatabaseEditor.this.hidden); 2822 list.setListData(tables.toArray()); 2823 } 2824 }); 2825 } 2826 }); 2827 buttons.add(new JButton("Show Hidden Tables") { 2828 final static long serialVersionUID = 42; 2829 2830 { 2831 addActionListener(new ActionListener() { 2832 public void actionPerformed(ActionEvent e) { 2833 DatabaseEditor.this.hidden = !DatabaseEditor.this.hidden; 2834 setText(DatabaseEditor.this.hidden 2835 ? "Hide Hidden Tables" 2836 : "Show Hidden Tables"); 2837 initList(DatabaseEditor.this.hidden); 2838 list.setListData(tables.toArray()); 2839 } 2840 }); 2841 } 2842 }); 2843 buttons.add(new JButton("Open Command Window") { 2844 final static long serialVersionUID = 42; 2845 2846 { 2847 addActionListener(new ActionListener() { 2848 public void actionPerformed(ActionEvent e) { 2849 makeCommandWindow("SQL Command Window", ""); 2850 } 2851 }); 2852 } 2853 }); 2854 buttons.add(new JButton("Open Database") { 2855 final static long serialVersionUID = 42; 2856 2857 { 2858 addActionListener(new ActionListener() { 2859 public void actionPerformed(ActionEvent e) { 2860 makeTableList(null); 2861 } 2862 }); 2863 } 2864 }); 2865 buttons.add(new JButton("Read Database") { 2866 final static long serialVersionUID = 42; 2867 2868 { 2869 addActionListener(new ActionListener() { 2870 public void actionPerformed(ActionEvent e) { 2871 File file = chooseFile("Read", new String[][]{}); 2872 if(file != null) { 2873 backupFile = file; 2874 String d = file.getParent(); 2875 if(d != null) currentDirectory = d; 2876 String fileString = readFileIntoString(backupFile); 2877 if(fileString != null) { 2878 int start = 0; 2879 int end = 0; 2880 Statement stat = null; 2881 try { 2882 do { 2883 end = getEndOfSqlStatement(fileString, start); 2884 stat = conn.createStatement(); 2885 stat.execute(fileString.substring(start, end)); 2886 int count = stat.getUpdateCount(); 2887 if(count >= 0) { 2888 //status.setText("Update count = " + count); 2889 } else { 2890 ResultSet rs = stat.getResultSet(); 2891 if(rs != null) { 2892 //status.setText("Result Set Generated"); 2893 JFrame frame = new Table(rs, "SQL Command Result"); 2894 frame.pack(); 2895 frame.setVisible(true); 2896 } 2897 } 2898 stat.close(); 2899 start = end; 2900 } while(start + 3 < fileString.length()); 2901 System.out.println("Done!"); 2902 } catch(SQLException ex) { 2903 int startLine = lineNumber(fileString, start); 2904 int endLine = lineNumber(fileString, end); 2905 System.err.println("error at lines " + startLine + " to " + endLine + "."); 2906 System.err.println(fileString.substring(start, end)); 2907 System.err.println(ex.getMessage()); 2908 } finally { 2909 try{ 2910 if(stat != null) stat.close(); 2911 } catch(SQLException closeex) { 2912 closeex.printStackTrace(); 2913 } 2914 } 2915 } 2916 } 2917 } 2918 }); 2919 } 2920 }); 2921 buttons.add(new JButton("Write Database") { 2922 final static long serialVersionUID = 42; 2923 2924 { 2925 addActionListener(new ActionListener() { 2926 public void actionPerformed(ActionEvent e) { 2927 File file = chooseFile("Write", new String[][]{}); 2928 if(file != null) { 2929 backupFile = file; 2930 String d = file.getParent(); 2931 if(d != null) currentDirectory = d; 2932 OutputStream out = null; 2933 try { 2934 out = new FileOutputStream(backupFile); 2935 dumpDatabase(out); 2936 } catch(IOException ex) { 2937 ex.printStackTrace(); 2938 } finally { 2939 close(out); 2940 } 2941 } 2942 } 2943 }); 2944 } 2945 }); 2946 getContentPane().add(buttons, BorderLayout.SOUTH); 2947 } 2948 2949 /** 2950 * Using the sqlite_master and sqlite_temp_master tables construct a 2951 * list of all tables in the attached databases. 2952 * 2953 * Currently assume table and database names do not contain "." or 2954 * use "." in db.name 2955 * 2956 * Note that the sqlite_master and sqlite_temp_master tables are not 2957 * in the list of tables so they have to be added separately. 2958 * 2959 * @param hidden true if listing hidden tables 2960 */ 2961 void initList(boolean hidden) { 2962 tables = new ArrayList<DatabaseTableEntry>(); 2963 try { 2964 Statement dbstat = conn.createStatement(); 2965 ResultSet dbrs = dbstat.executeQuery("pragma database_list;"); 2966 ResultSetMetaData dbmd = dbrs.getMetaData(); 2967 while(dbrs.next()) { 2968 String database = dbrs.getString(2); 2969 String master = database.equals("temp") ? "sqlite_temp_master" 2970 : "sqlite_master"; 2971 if(hidden) { 2972 tables.add(new DatabaseTableEntry(database, master, "table")); 2973 } 2974 Statement stat = conn.createStatement(); 2975 ResultSet rs = stat.executeQuery("select * from \"" 2976 + database + "\".\"" + master 2977 + "\";"); 2978 ResultSetMetaData md = rs.getMetaData(); 2979 while (rs.next()) { 2980 String type = rs.getString(1); 2981 if(type.equals("table") || type.equals("view")) { 2982 String tableName = rs.getString(2); 2983 if(hidden || tableName.indexOf("sqlite_") != 0) { 2984 tables.add(new DatabaseTableEntry(database, tableName, type)); 2985 } 2986 } 2987 } 2988 stat.close(); 2989 } 2990 dbstat.close(); 2991 Collections.sort(tables); 2992 } catch(SQLException ex) { 2993 System.err.println("Editor initialization failed for " + ex); 2994 ex.printStackTrace(); 2995 } 2996 } 2997 2998 /** 2999 * View or edit a sqlite database. Note that there are two character 3000 * sets - one for the database and one for the file system. They 3001 * both default to UTF-8. 3002 * 3003 * Usage: java DatabaseEditor <dbname> [<dbCharset> [<File System charset>]] 3004 * 3005 * @param args The command line arguments. 3006 */ 3007 public static void main(String[] args) { 3008 try { 3009 Class.forName("org.sqlite.JDBC"); 3010 } catch(ClassNotFoundException ex) { 3011 ex.printStackTrace(); 3012 } 3013 if(args.length > 3) { 3014 System.err.println("Usage: java DatabaseEditor <dbname> [<dbCharset> [<File System charset]]"); 3015 return; 3016 } 3017 if(args.length > 2) { 3018 fileCharset = Charset.availableCharsets().get(args[1]); 3019 if(fileCharset == null) { 3020 System.err.println("Unknown charset: " + args[1]); 3021 System.err.println(); 3022 System.err.println("Available charsets are: " + 3023 Charset.availableCharsets().keySet()); 3024 System.err.println(); 3025 System.err.println("Default charset is: " + Charset.defaultCharset()); 3026 return; 3027 } 3028 } 3029 3030 if(args.length > 1) { 3031 dbCharset = Charset.availableCharsets().get(args[1]); 3032 if(dbCharset == null) { 3033 System.err.println("Unknown charset: " + args[1]); 3034 System.err.println(); 3035 System.err.println("Available charsets are: " + 3036 Charset.availableCharsets().keySet()); 3037 System.err.println(); 3038 System.err.println("Default charset is: " + Charset.defaultCharset()); 3039 return; 3040 } 3041 } 3042 String db = args.length > 0 ? args[0] : null; 3043 SwingUtilities.invokeLater(new Runnable() { 3044 public void run() { 3045 makeTableList(db); 3046 } 3047 }); 3048 } 3049} // class DatabaseEditor extends JFrame