Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures


Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips


Submit News
Search the site here...

Swing Chapter 20. (Advanced topics) Constructing a Word Processor. Easy for reading, Click here!

Custom Search
Swing Chapter 20. (Advanced topics) Constructing a Word Processor. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 9/9 

Previous Page Previous Page (8/9)
Subpages: 1. Word Processor: part I - Introducing RTF
2. Word Processor: part II - Managing fonts
3. Word Processor: part III - Colors and images
4. Word Processor: part IV - Working with styles
5. Word Processor: part V - Clipboard and undo/redo
6. Word Processor: part VI - Advanced font mangement
7. Word Processor: part VII - Paragraph formatting
8. Word Processor: part VIII - Find and replace
9. Word Processor: part IX - Spell checker [using JDBC and SQL]

20.9  Word Processor: part IX - Spell checker [using JDBC and SQL]

All modern word processor applications worth mentioning offer tools and utilities which help the user in finding grammatical and spelling mistakes in a document. In this section we will add spell-checking to our word processor application. To do this we will need to perform some of our own multithreading, and communicate with JDBC. We will use a simple database with one table, Data, which has the following structure:

Name               Type                 Description

word                 String                A single English word

soundex                        String                A 4-letter SOUNDEX code

An example of this database, populated with words from several Shakespeare comedies and tragedies, is provided in this example's directory: Shakespeare.mdb. (This database must be a registered database in your database manager prior to using it. This is not a JDBC tutorial, so we'll skip the details.)

Figure 20.12 WordProcessor with spell checking functionality.

<<file figure20-12.gif>>

Note: The custom SOUNDEX algorithm used in this example hashes words for efficiency by using a simple model which approximates the sound of the word when spoken. Each word is reduced to a four character string, the first character being an upper case letter and the remaining three being digits.

The Code: WordProcessor.java

see \Chapter20\9

import java.awt.*;

import java.awt.event.*;

import java.io.*;

import java.util.*;

import java.sql.*;

import javax.swing.*;

import javax.swing.text.*;

import javax.swing.event.*;

import javax.swing.border.*;

import javax.swing.text.rtf.*;

import javax.swing.undo.*;

import dl.*;

public class WordProcessor extends JFrame


  // Unchanged code from section 20.8

  protected JMenuBar createMenuBar() {

    // Unchanged code from section 20.8

    JMenu mTools = new JMenu("Tools");


    Action spellAction = new AbstractAction("Spelling...",

     new ImageIcon("tools_abc.gif"))


      public void actionPerformed(ActionEvent e) {

        SpellChecker checker = new SpellChecker(WordProcessor.this);






    item =  mTools.add(spellAction); 



      KeyEvent.VK_F7, 0));



    m_toolBar.add(new SmallButton(spellAction,

      "Spell checker"));

    getContentPane().add(m_toolBar, BorderLayout.NORTH);

    return menuBar;


  // Unchanged code from section 20.8


class OpenList extends JPanel

 implements ListSelectionListener, ActionListener


  // Unchanged code from section 20.6

  public OpenList(String title, int numCols) {


    m_title = new JLabel(title, JLabel.LEFT);


    m_text = new JTextField(numCols);



    m_list = new JList();



    m_scroll = new JScrollPane(m_list);



  public void appendResultSet(ResultSet results, int index,

   boolean toTitleCase)



    DefaultListModel model = new DefaultListModel();

    try {

      while (results.next()) {

        String str = results.getString(index);

        if (toTitleCase)

          str = Utils.titleCase(str);




    catch (SQLException ex) {

      System.err.println("appendResultSet: "+ex.toString());



    if (model.getSize() > 0)



  // Unchanged code from section 20.6


// Unchanged code from section 20.6

class SpellChecker extends Thread


  protected static String SELECT_QUERY =

    "SELECT Data.word FROM Data WHERE Data.word = ";

  protected static String SOUNDEX_QUERY =

    "SELECT Data.word FROM Data WHERE Data.soundex = ";

  protected WordProcessor m_owner;

  protected Connection m_conn;

  protected DocumentTokenizer m_tokenizer;

  protected Hashtable  m_ignoreAll;

  protected SpellingDialog m_dlg;

  public SpellChecker(WordProcessor owner) {

    m_owner = owner;


  public void run() {

    JTextPane monitor = m_owner.getTextPane();



    m_dlg = new SpellingDialog(m_owner);

    m_ignoreAll = new Hashtable();

    try {

      // Load the JDBC-ODBC bridge driver


      m_conn = DriverManager.getConnection(

        "jdbc:odbc:Shakespeare", "admin", "");

      Statement selStmt = m_conn.createStatement();

      Document doc = m_owner.getDocument();

      int pos = monitor.getCaretPosition();

      m_tokenizer = new DocumentTokenizer(doc, pos);

      String word, wordLowCase;

      while (m_tokenizer.hasMoreTokens()) {

        word = m_tokenizer.nextToken();

        if (word.equals(word.toUpperCase()))


        if (word.length()<=1)


        if (Utils.hasDigits(word))


        wordLowCase = word.toLowerCase();

        if (m_ignoreAll.get(wordLowCase) != null)


        ResultSet results = selStmt.executeQuery(


        if (results.next())


        results = selStmt.executeQuery(SOUNDEX_QUERY+



          m_tokenizer.getEndPos(), false);

        if (!m_dlg.suggest(word, results))







    catch (Exception ex) {


      System.err.println("SpellChecker error: "+ex.toString());







  protected void replaceSelection(String replacement) {

    int xStart = m_tokenizer.getStartPos();

    int xFinish = m_tokenizer.getEndPos();

    m_owner.setSelection(xStart, xFinish, false);


    xFinish = xStart+replacement.length();

    m_owner.setSelection(xStart, xFinish, false);



  protected void addToDB(String word) {

    String sdx = Utils.soundex(word);

    try {

      Statement stmt = m_conn.createStatement();


        "INSERT INTO DATA (Word, Soundex) VALUES ('"+

        word+"', '"+sdx+"')");


    catch (Exception ex) {


      System.err.println("SpellChecker error: "+ex.toString());



  class SpellingDialog extends JDialog


    protected JTextField  m_txtNotFound;

    protected OpenList    m_suggestions;

    protected String   m_word;

    protected boolean  m_continue;

    public SpellingDialog(WordProcessor owner) {

      super(owner, "Spelling", true);

      JPanel p = new JPanel();

      p.setBorder(new EmptyBorder(5, 5, 5, 5));

      p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));

      p.add(new JLabel("Not in dictionary:"));


      m_txtNotFound = new JTextField();



      getContentPane().add(p, BorderLayout.NORTH);

      m_suggestions = new OpenList("Change to:", 12);

      m_suggestions.setBorder(new EmptyBorder(0, 5, 5, 5));

      getContentPane().add(m_suggestions, BorderLayout.CENTER);

      JPanel p1 = new JPanel();

      p1.setBorder(new EmptyBorder(20, 0, 5, 5));

      p1.setLayout(new FlowLayout());

      p = new JPanel(new GridLayout(3, 2, 8, 2));

      JButton bt = new JButton("Change");

      ActionListener lst = new ActionListener() {

        public void actionPerformed(ActionEvent e) {


          m_continue = true;







      bt = new JButton("Add");

      lst = new ActionListener() {

        public void actionPerformed(ActionEvent e) {


          m_continue = true;







      bt = new JButton("Ignore");

      lst = new ActionListener() {

        public void actionPerformed(ActionEvent e) {

          m_continue = true;







      bt = new JButton("Suggest");

      lst = new ActionListener() {

        public void actionPerformed(ActionEvent e) {

          try {

            m_word = m_suggestions.getSelected();

            Statement selStmt = m_conn.createStatement();

            ResultSet results = selStmt.executeQuery(


            boolean toTitleCase = Character.isUpperCase(


            m_suggestions.appendResultSet(results, 1,



          catch (Exception ex) {


            System.err.println("SpellChecker error: "+








      bt = new JButton("Ignore All");

      lst = new ActionListener() {

        public void actionPerformed(ActionEvent e) {

          m_ignoreAll.put(m_word.toLowerCase(), m_word);

          m_continue = true;







      bt = new JButton("Close");

      lst = new ActionListener() {

        public void actionPerformed(ActionEvent e) {

          m_continue = false;








      getContentPane().add(p1, BorderLayout.EAST);



      Dimension d1 = getSize();

      Dimension d2 = owner.getSize();

      int x = Math.max((d2.width-d1.width)/2, 0);

      int y = Math.max((d2.height-d1.height)/2, 0);

      setBounds(x + owner.getX(),

        y + owner.getY(), d1.width, d1.height);


    public boolean suggest(String word, ResultSet results) {

      m_continue = false;

      m_word = word;


      boolean toTitleCase = Character.isUpperCase(


      m_suggestions.appendResultSet(results, 1, toTitleCase);


      return m_continue;




class DocumentTokenizer


  protected Document m_doc;

  protected Segment  m_seg;

  protected int m_startPos;

  protected int m_endPos;

  protected int m_currentPos;

  public DocumentTokenizer(Document doc, int offset) {

    m_doc = doc;

    m_seg = new Segment();



  public boolean hasMoreTokens() {

    return (m_currentPos < m_doc.getLength());


  public String nextToken() {

    StringBuffer s = new StringBuffer();

    try {

      // Trim leading seperators

      while (hasMoreTokens()) {

        m_doc.getText(m_currentPos, 1, m_seg);

        char ch = m_seg.array[m_seg.offset];

        if (!Utils.isSeparator(ch)) {

          m_startPos = m_currentPos;





      // Append characters

      while (hasMoreTokens()) {

        m_doc.getText(m_currentPos, 1, m_seg);

        char ch = m_seg.array[m_seg.offset];

        if (Utils.isSeparator(ch)) {

          m_endPos = m_currentPos;







    catch (BadLocationException ex) {

      System.err.println("nextToken: "+ex.toString());

      m_currentPos = m_doc.getLength();


    return s.toString();


  public int getStartPos() { return m_startPos; }

  public int getEndPos() { return m_endPos; }

  public void setPosition(int pos) {

    m_startPos = pos;

    m_endPos = pos;

    m_currentPos = pos;



class Utils


  // Unchanged code from section 20.8

  public static String soundex(String word) {

    char[] result = new char[4];

    result[0] = word.charAt(0);

    result[1] = result[2] = result[3] = '0';

    int index = 1;

    char codeLast = '*';

    for (int k=1; k<word.length(); k++) {

      char ch = word.charAt(k);

      char code = ' ';

      switch (ch) {

        case 'b': case 'f': case 'p': case 'v':

          code = '1';


        case 'c': case 'g': case 'j': case 'k':

        case 'q': case 's': case 'x': case 'z':

          code = '2';


        case 'd': case 't':

          code = '3';


        case 'l':

          code = '4';


        case 'm': case 'n':

          code = '5';


        case 'r':

          code = '6';



          code = '*';



      if (code == codeLast)

        code = '*';

      codeLast = code;

      if (code != '*') {

        result[index] = code;


        if (index > 3)




    return new String(result);


  public static boolean hasDigits(String word) {

    for (int k=1; k<word.length(); k++) {

      char ch = word.charAt(k);

      if (Character.isDigit(ch))

        return true;


    return false;


  public static String titleCase(String source) {

    return Character.toUpperCase(source.charAt(0)) +




Understanding the Code

Class WordProcessor

This class now imports the java.sql package to make use of JDBC functionality. The createMenuBar() method now creates a new menu titled "Tools," which contains one menu item titled "Spelling..." This menu item can be invoked with keyboard accelerator F7, or by pressing the coresponding toolbar button. When selected, this new menu item creates and starts the SpellChecker thread (see below), passing a reference to the main application frame as parameter.

Class OpenList

This custom component receives new functionality for use in our new spell checker dialog. First, we add a new constructor which assigns a given number of columns to the text field, and does not initialize the list component.

Second, we add the appendResultSet() method which populates the list component with the data supplied in the given ResultSet instance at the given position. If the third parameter is set to true, this tells the method to convert all string data to the 'title case' (which means that the first letter is in upper case, and the rest of the string is unchanged). This is accomplished through use of our new titleCase() method in our Utils class (see below).

Class SpellChecker

This class extends Thread to perform spell checking of the current document from the current caret position moving downward. Two class variables are declared (their use will become more clear below):

String SELECT_QUERY: SQL query text used to select a word equal to a given string.

String SOUNDEX_QUERY: SQL query text used to select a word matching a given SOUNDEX value.

Five instance variables are declared:

WordProcessor m_owner: a reference to the main application frame.

Connection m_conn: JDBC connection to a database.

DocumentTokenizer m_tokenizer: a custom object used to retrieve each word in a document.

Hashtable m_ignoreAll: a collection of words to ignore in a search, added to with the "Ignore All" button.

SpellingDialog m_dlg: our custom dialog used for processing spelling mistakes.

The SpellChecker constructor takes a reference to the application's frame as a parameter and stores it in the m_owner instance variable.

The run() method is responsible for the most significant activity of this thread. To prevent the user from modifying the document during spell checking we first disable the main application frame and our text pane contained within it.

Note: Unlike AWT, Swing containers do not disable their child components when they themselves are disabled. It is not clear whether this is a bug or an intended feature.

Then we create a new SpellingDialog instance to provide the user interface, and instantiate the m_ignoreAll collection. In a try/catch block we process all JDBC interactions to allow proper handling of any potential errors. This code creates a JDBC connection to our Shakespeare database, retrieves the current caret position, and creates an instance of DocumentTokenizer to parse the document from the current caret position. In a while loop we perform spell checking on each word fetched until there are no more tokens. Words in all upper case, containing only one letter, or containing digits, are skipped (this behavior can easily be customized). Then we convert the word under examination to lower case and search for it in the m_ignoreAll collection. If it is not found, we try to find it in the database. If the SQL query does not return any results, we try to locate a similar word in the database with the same SOUNDEX value to suggest to the user in the dialog. The word in question is then selected in our text pane to show the user which word is currently under examination. Finally we call our SpellingDialog's suggest() method to request that the user make a decision about what to do with this word. If the suggest() method returns false, the user has chosen to terminate the spell checking process, so we exit the loop. Once outside the loop we close the JDBC connection, restore the original caret position, explicitly call the garbage collector, and reenable the main application frame and our text pane editor contained within it.

The following two methods are invoked by the SpellingDialog instance associated with this SpellChecker:

replaceSelection() is used to replace the most recently parsed word by the DocumentTokenizer instance, with the given replacement string.

addToDB() adds a given word, and it's SOUNDEX value, to the database by executing an insert query.

Class SpellChecker.SpellingDialog

This inner class represents a dialog which prompts the user to verify or correct a certain word if it is not found in the database. The user can select one of several actions in response: ignore the given word, ignore all occurrences of that word in the document, replace that word with another word, add this word to the database and consider it correct in any future matches, or cancel the spell check. Four instance variables are declared:

JTextField m_txtNotFound: used to display the word under investigation.

OpenList m_suggestions: editable list component to select or enter a replacement word.

String m_word: the word under investigation.

boolean m_continue: a flag indicating that spell checking should continue.

The SpellingDialog constructor places the m_txtNotFound component and corresponding label at the top of the dialog window. The m_suggestions OpenList is placed in the center, and six buttons discussed below, are grouped to the right.

The button titled "Change" replaces the word under investigation with the word currently selected in the list or entered by the user. Then it stores true in the m_continue flag and hides the dialog window. This terminates the modal state of the dialog and makes the show() method return, which in turn allows the program's execution to continue (recall that modal dialogs block the calling thread until they are dismissed).

The button titled "Add" adds the word in question to the spelling database. This word will then be considered correct in future queries. In this way we allow the spell checker to "learn" new words (i.e. add them to the dictionary).

The button titled "Suggest" populates the m_suggestions list with all SOUNDEX matches to the word under investigation. This button is intended for use in situations where the user is not satisfied with the initial suggestions.

The button titled "Ignore" simply skips the current word and continues spell checking the remaining text.

The button titled "Ignore All" does the same as the "Ignore" button, but also stores the word in question in the collection of words to ignore, so the next time the spell checker finds this word it will be deemed correct. The difference between "Ignore All" and "Add" is that ignored words will only be ignored during a single spell check, whereas words added to the database will persist as long as the database data does.

The button titled "Close" stores false in the m_continue flag and hides the dialog window. This results in the termination of the spell checking process (see the suggest() method).

The suggest() method is used to display this SpellingDialog each time a questionable word is located during the spell checking process. It takes a String and a ResultSet containing suggested substitutions as parameters. It sets the text of the m_txtNotFound component to the String passed in, and calls appendResultSet() on the OpenList to display an array of suggested corrections. Note that the first character of these suggestions will be converted to upper case if the word in question starts with a capital letter. Finally, the show() method displays this dialog in the modal state. As soon as this state is terminated by one of the push buttons, or by directly closing the dialog, the suggest() method returns the m_continue flag. If this flag is set to false, this indicates that the calling program should terminate the spell checking cycle.

Class DocumentTokenizer

This helper class was built to parse the current text pane document. Unfortunately we cannot use the standard StreamTokenizer class for this purpose, because it provides no way of querying the position of a token within the document (we need this information to allow word replacement). Several instance variables are declared:

Document m_doc: a reference to the document to be parsed.

Segment m_seg: used for quick delivery of characters from the document being parsed.

int m_startPos: the start position of the current word from the beginning of the document.

int m_endPos: the end position of the current word from the beginning of the document.

int m_currentPos: the current position of the parser from the beginning of the document.

The DocumentTokenizer constructor takes a reference to the document to be parsed and the offset to start at as parameters. It initializes the instance variables described above.

The hasMoreTokens() method returns true if the current parsing position lies within the document.

The nextToken() method extracts the next token (a group of characters separated by one or more characters defined in the WORD_SEPARATORS array from our Utils class) and returns it as a String. The positions of the beginning and the end of the token are stored in the m_startPos and m_endPos instance variables respectively. To access a portion of document text with the least possible overhead we use the Document.getText() method which takes three parameters: offset from the beginning of the document, length of the text fragment, and a reference to an instance of the Segment class (recall from chapter 19 that the Segment class provides an efficient means of directly accessing an array of document characters).

We look at each character in turn, passing over separator characters until the first non-separator character is reached. This position is marked as the beginning of a new word. Then a StringBuffer is used to accumulate characters until a separator character, or the end of document, is reached. The resulting characters are returned as a String.

Note: This variant of the getText() method gives us direct access to the characters contained in the document through a Segment instance. These characters should not be modified.

Class Utils

Three new static methods are added to this class. The soundex() method calculates and returns the SOUNDEX code of the given word. To calculate that code we use the first character of the given word and add a three-digit code that represents the first three remaining consonants. The conversion is made according to the following table:

Code       Letters

1              B,P,F,V

2              C,S,G,J,K,Q,X,Z

3              D,T

4              L

5              M,N

6              R

*              (all others)

The hasDigits() method returns true if a given string contains digits, and the titleCase() method converts the first character of a given string to upper case.

Running the Code

Open an existing RTF file and try running a complete spell check. Try adding some words to the dictionary and use the "Ignore All" button to avoid questioning a word again during that spell check. Try using the "Suggest" button to query the database for more suggestions based on our SOUNDEX algorithm. Click the "Change" button to accept a suggestion or a change typed into the text field. Click the "Ignore" button to ignore the current word being questioned.

Note: The Shakespeare vocabulary database supplied for this example is neither complete nor contemporary. It does not include such words as "software" or "Internet." However, you can easily add them, when encountered during a spell check, by clicking the "Add" button.

Expect to see complete HTML coverage and examples in a future edition of this book. Also keep an eye on the Swing Connection site for updates. As we go to print there are rumors of several HTML editor examples in the works.

[ Return to Swing (Book) ]

Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color

[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3

[ More in News Section ]

Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy