alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.editor;

import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.io.IOException;
import java.io.Writer;
import java.io.CharArrayWriter;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.BadLocationException;

/**
* Various services related to indentation and text formatting
* are located here. Each kit can have different formatter
* so the first action should be getting the right formatter
* for the given kit by calling Formatter.getFormatter(kitClass).
*
* @author Miloslav Metelka
* @version 1.00
*/

public class Formatter implements SettingsChangeListener {

    private static Map kitClass2Formatter = new HashMap();

    /** Get the formatter for the given kit-class */
    public static synchronized Formatter getFormatter(Class kitClass) {
        Formatter f = (Formatter)kitClass2Formatter.get(kitClass);
        if (f == null) {
            f = BaseKit.getKit(kitClass).createFormatter();
            kitClass2Formatter.put(kitClass, f);
        }
        return f;
    }
    
    /** Set the formatter for the given kit-class.
     * @param kitClass class of the kit for which the formatter
     *  is being assigned.
     * @param formatter new formatter for the given kit
     */
    public static synchronized void setFormatter(Class kitClass, Formatter formatter) {
        kitClass2Formatter.put(kitClass, formatter);
    }


    /** Maximum tab size for which the indent strings will be cached. */
    private static final int ISC_MAX_TAB_SIZE = 16;
    
    /** Cache the indentation strings up to this size */
    private static final int ISC_MAX_INDENT_SIZE = 32;
    
    /** Cache holding the indentation strings for various tab-sizes. */
    private static final String[][] indentStringCache
        = new String[ISC_MAX_TAB_SIZE][];


    private final Class kitClass;

    /** Whether values were already inited from the cache */
    private boolean inited;

    private int tabSize;

    private boolean customTabSize;

    private Integer shiftWidth;

    private boolean customShiftWidth;

    private boolean expandTabs;

    private boolean customExpandTabs;

    private int spacesPerTab;

    private boolean customSpacesPerTab;

    /** Construct new formatter.
    * @param kitClass the class of the kit for which this formatter is being
    *  constructed.
    */
    public Formatter(Class kitClass) {
        this.kitClass = kitClass;
        Settings.addSettingsChangeListener(this);
    }

    /** Get the kit-class for which this formatter is constructed. */
    public Class getKitClass() {
        return kitClass;
    }

    public void settingsChange(SettingsChangeEvent evt) {
        String settingName = (evt != null) ? evt.getSettingName() : null;
        if (!inited || settingName == null || SettingsNames.TAB_SIZE.equals(settingName)) {
            if (!customTabSize) {
                tabSize = SettingsUtil.getInteger(kitClass, SettingsNames.TAB_SIZE,
                                                  SettingsDefaults.defaultTabSize);
            }
        }

        // Shift-width often depends on the rest of parameters
        if (!customShiftWidth) {
            Object shw = Settings.getValue(kitClass, SettingsNames.INDENT_SHIFT_WIDTH);
            if (shw instanceof Integer) {
                shiftWidth = (Integer)shw;
            }
        }

        if (!inited || settingName == null || SettingsNames.EXPAND_TABS.equals(settingName)) {
            if (!customExpandTabs) {
                expandTabs = SettingsUtil.getBoolean(kitClass, SettingsNames.EXPAND_TABS,
                                                     SettingsDefaults.defaultExpandTabs);
            }
        }
        if (!inited || settingName == null || SettingsNames.SPACES_PER_TAB.equals(settingName)) {
            if (!customSpacesPerTab) {
                spacesPerTab = SettingsUtil.getInteger(kitClass, SettingsNames.SPACES_PER_TAB,
                                                       SettingsDefaults.defaultSpacesPerTab);
            }
        }

        inited = true;
    }

    /** Get the number of spaces the TAB character ('\t') visually represents
     * for non-BaseDocument documents. It shouldn't be used for BaseDocument
     * based documents. The reason for that is that the returned value 
     * reflects the value of the setting for the kit class over which
     * this formatter was constructed. However it's possible that the kit class of
     * the document being formatted is different than the kit of the formatter.
     * For example java document could be formatted by html formatter.
     * Therefore BaseDocument.getTabSize() must be used
     * for BaseDocuments to reflect the document's own tabsize.
     * @see BaseDocument.getTabSize()
     */
    public int getTabSize() {
        if (!customTabSize && !inited) {
            settingsChange(null);
        }

        return tabSize;
    }

    /** Set the number of spaces the TAB character ('\t') visually represents
     * for non-BaseDocument documents. It doesn't affect BaseDocument
     * based documents.
     *
     * @see getTabSize()
     * @see BaseDocument.setTabSize()
     */
    public void setTabSize(int tabSize) {
        customTabSize = true;
        this.tabSize = tabSize;
    }


    /** Get the width of one indentation level for non-BaseDocument documents.
     * The algorithm first checks whether there's a value for the INDENT_SHIFT_WIDTH
     * setting. If so it uses it, otherwise it uses getSpacesPerTab()
     * 
     * @see setShiftWidth()
     * @see getSpacesPerTab()
     */
    public int getShiftWidth() {
        if (!customShiftWidth && !inited) {
            settingsChange(null);
        }

        return (shiftWidth != null) ? shiftWidth.intValue() : getSpacesPerTab();
    }

    /** Set the width of one indentation level for non-BaseDocument documents.
     * It doesn't affect BaseDocument based documents.
     *
     * @see getShiftWidth()
     */
    public void setShiftWidth(int shiftWidth) {
        customShiftWidth = true;
        if (this.shiftWidth == null || this.shiftWidth.intValue() != shiftWidth) {
            this.shiftWidth = new Integer(shiftWidth);
        }
    }

    /** Should the typed tabs be expanded to the spaces? */
    public boolean expandTabs() {
        if (!customExpandTabs && !inited) {
            settingsChange(null);
        }

        return expandTabs;
    }

    public void setExpandTabs(boolean expandTabs) {
        customExpandTabs = true;
        this.expandTabs = expandTabs;
    }

    /** Get the number of spaces that should be inserted into the document
    * instead of one typed tab.
    */
    public int getSpacesPerTab() {
        if (!customSpacesPerTab && !inited) {
            settingsChange(null);
        }

        return spacesPerTab;
    }

    public void setSpacesPerTab(int spacesPerTab) {
        customSpacesPerTab = true;
        this.spacesPerTab = spacesPerTab;
    }

    static String getIndentString(int indent, boolean expandTabs, int tabSize) {
        if (indent <= 0) {
            return "";
        }

        if (expandTabs) { // store in 0th slot
            tabSize = 0;
        }

        synchronized (Settings.class) {
            boolean large = (tabSize >= indentStringCache.length)
                || (indent > ISC_MAX_INDENT_SIZE); // indexed by (indent - 1)
            String indentString = null;
            String[] tabCache = null;
            if (!large) {
                tabCache = indentStringCache[tabSize]; // cache for this tab
                if (tabCache == null) {
                    tabCache = new String[ISC_MAX_INDENT_SIZE];
                    indentStringCache[tabSize] = tabCache;
                }
                indentString = tabCache[indent - 1];
            }

            if (indentString == null) {
                indentString = Analyzer.getIndentString(indent, expandTabs, tabSize);

                if (!large) {
                    tabCache[indent - 1] = indentString;
                }
            }

            return indentString;
        }
    }

    public String getIndentString(BaseDocument doc, int indent) {
        return getIndentString(indent, expandTabs(), doc.getTabSize());
    }
        
        
    /** Get the string that is appropriate for the requested indentation.
    * The returned string respects the expandTabs() and
    * the getTabSize() and will contain either spaces only
    * or fully or partially tabs as necessary.
    */
    public String getIndentString(int indent) {
        return getIndentString(indent, expandTabs(), getTabSize());
    }

    /** Modify the line to move the text starting at dotPos one tab
     * column to the right.  Whitespace preceeding dotPos may be
     * replaced by a TAB character if tabs expanding is on.
     * @param doc document to operate on
     * @param dotPos insertion point
     */
    public void insertTabString(BaseDocument doc, int dotPos)
    throws BadLocationException {
        doc.atomicLock();
        try {
            // Determine first white char before dotPos
            int rsPos = Utilities.getRowStart(doc, dotPos);
            int startPos = Utilities.getFirstNonWhiteBwd(doc, dotPos, rsPos);
            startPos = (startPos >= 0) ? (startPos + 1) : rsPos;
            
            int startCol = Utilities.getVisualColumn(doc, startPos);
            int endCol = Utilities.getNextTabColumn(doc, dotPos);
            String tabStr = Analyzer.getWhitespaceString(startCol, endCol, expandTabs(), doc.getTabSize());
            
            // Search for the first non-common char
            char[] removeChars = doc.getChars(startPos, dotPos - startPos);
            int ind = 0;
            while (ind < removeChars.length && removeChars[ind] == tabStr.charAt(ind)) {
                ind++;
            }
            
            startPos += ind;
            doc.remove(startPos, dotPos - startPos);
            doc.insertString(startPos, tabStr.substring(ind), null);
            
        } finally {
            doc.atomicUnlock();
        }
    }

    /** Change the indent of the given row. Document is atomically locked
    * during this operation.
    */
    public void changeRowIndent(BaseDocument doc, int pos, int newIndent)
    throws BadLocationException {
        doc.atomicLock();
        try {
            if (newIndent < 0) {
                newIndent = 0;
            }
            int firstNW = Utilities.getRowFirstNonWhite(doc, pos);
            if (firstNW == -1) { // valid first non-blank
                firstNW = Utilities.getRowEnd(doc, pos);
            }
            int replacePos = Utilities.getRowStart(doc, pos);
            int removeLen = firstNW - replacePos;
            String removeText = doc.getText(replacePos, removeLen);
            String newIndentText = getIndentString(doc, newIndent);
            if (newIndentText.startsWith(removeText)) {
                // Skip removeLen chars at start
                newIndentText = newIndentText.substring(removeLen);
                replacePos += removeLen;
                removeLen = 0;
            } else if (newIndentText.endsWith(removeText)) {
                // Skip removeLen chars at the end
                newIndentText = newIndentText.substring(0, newIndentText.length() - removeLen);
                removeLen = 0;
            }
            
            if (removeLen != 0) {
                doc.remove(replacePos, removeLen);
            }

            doc.insertString(replacePos, newIndentText, null);
        } finally {
            doc.atomicUnlock();
        }
    }

    /** Increase/decrease indentation of the block of the code. Document
    * is atomically locked during the operation.
    * @param doc document to operate on
    * @param startPos starting line position
    * @param endPos ending line position
    * @param shiftCnt positive/negative count of shiftwidths by which indentation
    *   should be shifted right/left
    */
    public void changeBlockIndent(BaseDocument doc, int startPos, int endPos,
                                  int shiftCnt) throws BadLocationException {

       
        GuardedDocument gdoc = (doc instanceof GuardedDocument)
                               ? (GuardedDocument)doc : null;
        if (gdoc != null){
            for (int i = startPos; i 0 && Utilities.getRowStart(doc, endPos) == endPos) {
                endPos--;
            }

            int pos = Utilities.getRowStart(doc, startPos );
            for (int lineCnt = Utilities.getRowCount(doc, startPos, endPos);
                    lineCnt > 0; lineCnt--
                ) {
                int indent = Utilities.getRowIndent(doc, pos);
                if (Utilities.isRowWhite(doc, pos)) {
                    indent = -indentDelta; // zero indentation for white line
                }
                changeRowIndent(doc, pos, Math.max(indent + indentDelta, 0));
                pos = Utilities.getRowStart(doc, pos, +1);
            }

        } finally {
            doc.atomicUnlock();
        }
    }

    /** Shift line either left or right */
    public void shiftLine(BaseDocument doc, int dotPos, boolean right)
    throws BadLocationException {
        int ind = doc.getShiftWidth();
        if (!right) {
            ind = -ind;
        }

        if (Utilities.isRowWhite(doc, dotPos)) {
            ind += Utilities.getVisualColumn(doc, dotPos);
        } else {
            ind += Utilities.getRowIndent(doc, dotPos);
        }
        ind = Math.max(ind, 0);
        changeRowIndent(doc, dotPos, ind);
    }

    /** Reformat a block of code.
    * @param doc document to work with
    * @param startOffset offset at which the formatting starts
    * @param endOffset offset at which the formatting ends
    * @return length of the reformatted code
    */
    public int reformat(BaseDocument doc, int startOffset, int endOffset)
    throws BadLocationException {
        try {
            CharArrayWriter cw = new CharArrayWriter();
            Writer w = createWriter(doc, startOffset, cw);
            w.write(doc.getChars(startOffset, endOffset - startOffset));
            w.close();
            String out = new String(cw.toCharArray());
            doc.remove(startOffset, endOffset - startOffset);
            doc.insertString(startOffset, out, null);
            return out.length();
        } catch (IOException e) {
            Utilities.annotateLoggable(e);
            return 0;
        }
    }

    /** Indents the current line. Should not affect any other
    * lines.
    * @param doc the document to work on
    * @param offset the offset of a character on the line
    * @return new offset of the original character
    */
    public int indentLine(Document doc, int offset) {
        return offset;
    }

    /** Inserts new line at given position and indents the new line with
    * spaces.
    *
    * @param doc the document to work on
    * @param offset the offset of a character on the line
    * @return new offset to place cursor to
    */
    public int indentNewLine(Document doc, int offset) {
        try {
            doc.insertString(offset, "\n", null); // NOI18N
            offset++;

        } catch (GuardedException e) {
            java.awt.Toolkit.getDefaultToolkit().beep();

        } catch (BadLocationException e) {
            Utilities.annotateLoggable(e);
        }

        return offset;
    }

    /** Creates a writer that formats text that is inserted into it.
    * The writer should not modify the document but use the 
    * provided writer to write to. Usually the underlaying writer
    * will modify the document itself and optionally it can remember
    * the current position in document. That is why the newly created
    * writer should do no buffering.
    * 

* The provided document and pos are only informational, * should not be modified but only used to find correct indentation * strategy. * * @param doc document * @param offset position to begin inserts at * @param writer writer to write to * @return new writer that will format written text and pass it * into the writer */ public Writer createWriter(Document doc, int offset, Writer writer) { return writer; } }

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.