/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.editor.indent.api;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.PlainDocument;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.BaseKit;
import org.netbeans.editor.Formatter;
import org.netbeans.editor.Settings;
import org.netbeans.editor.SettingsNames;
import org.netbeans.lib.editor.util.ArrayUtilities;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.modules.editor.indent.IndentImpl;
/**
* Utility methods related to indentation and reformatting.
*
* @author Miloslav Metelka
*/
public final class IndentUtils {
private static final int MAX_CACHED_INDENT = 80;
private static final String[] cachedSpacesStrings = new String[MAX_CACHED_INDENT + 1];
static {
cachedSpacesStrings[0] = "";
}
private static final int MAX_CACHED_TAB_SIZE = 8; // Should mostly be <= 8
/**
* Cached indentation string containing tabs.
* <br/>
* The cache does not contain indents smaller than the particular tabSize
* since they are only spaces contained in cachedSpacesStrings.
*/
private static final String[][] cachedTabIndents = new String[MAX_CACHED_TAB_SIZE + 1][];
private IndentUtils() {
// no instances
}
/**
* Get number of spaces that form a single indentation level.
*
* @return >=0 size of indentation level in spaces.
*/
public static int indentLevelSize(Document doc) {
int indentLevel;
if (doc instanceof BaseDocument) {
indentLevel = ((BaseDocument)doc).getShiftWidth();
} else {
Object val = Settings.getValue(BaseKit.class, SettingsNames.INDENT_SHIFT_WIDTH);
indentLevel = (val instanceof Integer) ? ((Integer)val).intValue() : tabSize(doc);
}
return indentLevel;
}
/**
* Get number of spaces that visually substitute '\t' character.
*
* @return >=0 size corresponding to '\t' character in spaces.
*/
public static int tabSize(Document doc) {
int tabSize;
if (doc instanceof BaseDocument) {
tabSize = ((BaseDocument)doc).getTabSize();
} else {
Object val = doc.getProperty(PlainDocument.tabSizeAttribute);
tabSize = (val instanceof Integer) ? ((Integer)val).intValue() : 8;
}
assert (tabSize >= 0) : "Retrieved tabSize=" + tabSize + " < 0"; // NOI18N
return tabSize;
}
/**
* Get whether the indentation strings should contain hard tabs '\t'
* or whether they should only contain spaces.
*
* @return true if the tabs should be expanded or false if not.
*/
public static boolean isExpandTabs(Document doc) {
if (doc instanceof BaseDocument) {
Formatter formatter = ((BaseDocument)doc).getFormatter();
return (formatter != null) ? formatter.expandTabs() : true;
} else
return true;
}
/**
* Get start offset of a line in a document.
*
* @param doc non-null document.
* @param offset >= 0 offset anywhere on the line.
* @throws BadLocationException for invalid offset
*/
public static int lineStartOffset(Document doc, int offset) throws BadLocationException {
IndentImpl.checkOffsetInDocument(doc, offset);
Element lineRootElement = IndentImpl.lineRootElement(doc);
return lineRootElement.getElement(lineRootElement.getElementIndex(offset)).getStartOffset();
}
/**
* Get indentation of a line in a document as a number of spaces.
*
* @param doc non-null document.
* @param lineStartOffset >= 0 start offset of a line in the document.
* @throws BadLocationException for invalid offset
*/
public static int lineIndent(Document doc, int lineStartOffset) throws BadLocationException {
IndentImpl.checkOffsetInDocument(doc, lineStartOffset);
CharSequence docText = DocumentUtilities.getText(doc);
int indent = 0;
int tabSize = -1;
while (lineStartOffset < docText.length()) {
char ch;
switch (ch = docText.charAt(lineStartOffset)) {
case '\n':
return indent;
case '\t':
if (tabSize == -1)
tabSize = tabSize(doc);
// Round to next tab stop
indent = (indent + tabSize) / tabSize * tabSize;
break;
default:
if (Character.isWhitespace(ch))
indent++;
else
return indent;
}
lineStartOffset++;
}
return indent;
}
/**
* Create (or get from cache) indentation string for the given indent.
* <br/>
* The indentation settings (tab-size etc. are determined based on the given
* document).
*
* @param doc document from which the indentation settings will be retrieved.
* @param indent >=0 indentation in number of spaces.
* @return indentation string containing tabs and spaces according to the document's
* settings (tab-size etc.).
*/
public static String createIndentString(Document doc, int indent) {
if (indent < 0)
throw new IllegalArgumentException("indent=" + indent + " < 0"); // NOI18N
return cachedOrCreatedIndentString(indent, isExpandTabs(doc), tabSize(doc));
}
static String cachedOrCreatedIndentString(int indent, boolean expandTabs, int tabSize) {
String indentString;
if (expandTabs || (indent < tabSize)) {
if (indent <= MAX_CACHED_INDENT) {
synchronized (cachedSpacesStrings) {
indentString = cachedSpacesStrings[indent];
if (indentString == null) {
// Create string with MAX_CACHED_SPACES spaces first if not cached yet
indentString = cachedSpacesStrings[MAX_CACHED_INDENT];
if (indentString == null) {
indentString = createSpacesString(MAX_CACHED_INDENT);
cachedSpacesStrings[MAX_CACHED_INDENT] = indentString;
}
indentString = indentString.substring(0, indent);
cachedSpacesStrings[indent] = indentString;
}
}
} else {
indentString = createSpacesString(indent);
}
} else { // Do not expand tabs
if (indent <= MAX_CACHED_INDENT && tabSize <= MAX_CACHED_TAB_SIZE) {
synchronized (cachedTabIndents) {
String[] tabIndents = cachedTabIndents[tabSize];
if (tabIndents == null) {
// Do not cache spaces-only strings
tabIndents = new String[MAX_CACHED_INDENT - tabSize];
cachedTabIndents[tabSize] = tabIndents;
}
indentString = tabIndents[indent - tabSize];
if (indentString == null) {
indentString = createTabIndentString(indent, tabSize);
tabIndents[indent - tabSize] = indentString;
}
}
} else {
indentString = createTabIndentString(indent, tabSize);
}
}
return indentString;
}
private static String createSpacesString(int spaceCount) {
StringBuilder sb = new StringBuilder(spaceCount);
ArrayUtilities.appendSpaces(sb, spaceCount);
return sb.toString();
}
private static String createTabIndentString(int indent, int tabSize) {
StringBuilder sb = new StringBuilder();
while (indent >= tabSize) {
sb.append('\t');
indent -= tabSize;
}
ArrayUtilities.appendSpaces(sb, indent);
return sb.toString();
}
}
|