001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.editor.indent.api;
043:
044: import javax.swing.text.BadLocationException;
045: import javax.swing.text.Document;
046: import javax.swing.text.Element;
047: import javax.swing.text.PlainDocument;
048: import org.netbeans.editor.BaseDocument;
049: import org.netbeans.editor.BaseKit;
050: import org.netbeans.editor.Formatter;
051: import org.netbeans.editor.Settings;
052: import org.netbeans.editor.SettingsNames;
053: import org.netbeans.lib.editor.util.ArrayUtilities;
054: import org.netbeans.lib.editor.util.swing.DocumentUtilities;
055: import org.netbeans.modules.editor.indent.IndentImpl;
056:
057: /**
058: * Utility methods related to indentation and reformatting.
059: *
060: * @author Miloslav Metelka
061: */
062: public final class IndentUtils {
063:
064: private static final int MAX_CACHED_INDENT = 80;
065:
066: private static final String[] cachedSpacesStrings = new String[MAX_CACHED_INDENT + 1];
067: static {
068: cachedSpacesStrings[0] = "";
069: }
070:
071: private static final int MAX_CACHED_TAB_SIZE = 8; // Should mostly be <= 8
072:
073: /**
074: * Cached indentation string containing tabs.
075: * <br/>
076: * The cache does not contain indents smaller than the particular tabSize
077: * since they are only spaces contained in cachedSpacesStrings.
078: */
079: private static final String[][] cachedTabIndents = new String[MAX_CACHED_TAB_SIZE + 1][];
080:
081: private IndentUtils() {
082: // no instances
083: }
084:
085: /**
086: * Get number of spaces that form a single indentation level.
087: *
088: * @return >=0 size of indentation level in spaces.
089: */
090: public static int indentLevelSize(Document doc) {
091: int indentLevel;
092: if (doc instanceof BaseDocument) {
093: indentLevel = ((BaseDocument) doc).getShiftWidth();
094: } else {
095: Object val = Settings.getValue(BaseKit.class,
096: SettingsNames.INDENT_SHIFT_WIDTH);
097: indentLevel = (val instanceof Integer) ? ((Integer) val)
098: .intValue() : tabSize(doc);
099: }
100: return indentLevel;
101: }
102:
103: /**
104: * Get number of spaces that visually substitute '\t' character.
105: *
106: * @return >=0 size corresponding to '\t' character in spaces.
107: */
108: public static int tabSize(Document doc) {
109: int tabSize;
110: if (doc instanceof BaseDocument) {
111: tabSize = ((BaseDocument) doc).getTabSize();
112: } else {
113: Object val = doc
114: .getProperty(PlainDocument.tabSizeAttribute);
115: tabSize = (val instanceof Integer) ? ((Integer) val)
116: .intValue() : 8;
117: }
118: assert (tabSize >= 0) : "Retrieved tabSize=" + tabSize + " < 0"; // NOI18N
119: return tabSize;
120: }
121:
122: /**
123: * Get whether the indentation strings should contain hard tabs '\t'
124: * or whether they should only contain spaces.
125: *
126: * @return true if the tabs should be expanded or false if not.
127: */
128: public static boolean isExpandTabs(Document doc) {
129: if (doc instanceof BaseDocument) {
130: Formatter formatter = ((BaseDocument) doc).getFormatter();
131: return (formatter != null) ? formatter.expandTabs() : true;
132: } else
133: return true;
134: }
135:
136: /**
137: * Get start offset of a line in a document.
138: *
139: * @param doc non-null document.
140: * @param offset >= 0 offset anywhere on the line.
141: * @throws BadLocationException for invalid offset
142: */
143: public static int lineStartOffset(Document doc, int offset)
144: throws BadLocationException {
145: IndentImpl.checkOffsetInDocument(doc, offset);
146: Element lineRootElement = IndentImpl.lineRootElement(doc);
147: return lineRootElement.getElement(
148: lineRootElement.getElementIndex(offset))
149: .getStartOffset();
150: }
151:
152: /**
153: * Get indentation of a line in a document as a number of spaces.
154: *
155: * @param doc non-null document.
156: * @param lineStartOffset >= 0 start offset of a line in the document.
157: * @throws BadLocationException for invalid offset
158: */
159: public static int lineIndent(Document doc, int lineStartOffset)
160: throws BadLocationException {
161: IndentImpl.checkOffsetInDocument(doc, lineStartOffset);
162: CharSequence docText = DocumentUtilities.getText(doc);
163: int indent = 0;
164: int tabSize = -1;
165: while (lineStartOffset < docText.length()) {
166: char ch;
167: switch (ch = docText.charAt(lineStartOffset)) {
168: case '\n':
169: return indent;
170:
171: case '\t':
172: if (tabSize == -1)
173: tabSize = tabSize(doc);
174: // Round to next tab stop
175: indent = (indent + tabSize) / tabSize * tabSize;
176: break;
177:
178: default:
179: if (Character.isWhitespace(ch))
180: indent++;
181: else
182: return indent;
183: }
184: lineStartOffset++;
185: }
186: return indent;
187: }
188:
189: /**
190: * Create (or get from cache) indentation string for the given indent.
191: * <br/>
192: * The indentation settings (tab-size etc. are determined based on the given
193: * document).
194: *
195: * @param doc document from which the indentation settings will be retrieved.
196: * @param indent >=0 indentation in number of spaces.
197: * @return indentation string containing tabs and spaces according to the document's
198: * settings (tab-size etc.).
199: */
200: public static String createIndentString(Document doc, int indent) {
201: if (indent < 0)
202: throw new IllegalArgumentException("indent=" + indent
203: + " < 0"); // NOI18N
204: return cachedOrCreatedIndentString(indent, isExpandTabs(doc),
205: tabSize(doc));
206: }
207:
208: static String cachedOrCreatedIndentString(int indent,
209: boolean expandTabs, int tabSize) {
210: String indentString;
211: if (expandTabs || (indent < tabSize)) {
212: if (indent <= MAX_CACHED_INDENT) {
213: synchronized (cachedSpacesStrings) {
214: indentString = cachedSpacesStrings[indent];
215: if (indentString == null) {
216: // Create string with MAX_CACHED_SPACES spaces first if not cached yet
217: indentString = cachedSpacesStrings[MAX_CACHED_INDENT];
218: if (indentString == null) {
219: indentString = createSpacesString(MAX_CACHED_INDENT);
220: cachedSpacesStrings[MAX_CACHED_INDENT] = indentString;
221: }
222: indentString = indentString
223: .substring(0, indent);
224: cachedSpacesStrings[indent] = indentString;
225: }
226: }
227: } else {
228: indentString = createSpacesString(indent);
229: }
230:
231: } else { // Do not expand tabs
232: if (indent <= MAX_CACHED_INDENT
233: && tabSize <= MAX_CACHED_TAB_SIZE) {
234: synchronized (cachedTabIndents) {
235: String[] tabIndents = cachedTabIndents[tabSize];
236: if (tabIndents == null) {
237: // Do not cache spaces-only strings
238: tabIndents = new String[MAX_CACHED_INDENT
239: - tabSize];
240: cachedTabIndents[tabSize] = tabIndents;
241: }
242: indentString = tabIndents[indent - tabSize];
243: if (indentString == null) {
244: indentString = createTabIndentString(indent,
245: tabSize);
246: tabIndents[indent - tabSize] = indentString;
247: }
248: }
249: } else {
250: indentString = createTabIndentString(indent, tabSize);
251: }
252: }
253: return indentString;
254: }
255:
256: private static String createSpacesString(int spaceCount) {
257: StringBuilder sb = new StringBuilder(spaceCount);
258: ArrayUtilities.appendSpaces(sb, spaceCount);
259: return sb.toString();
260: }
261:
262: private static String createTabIndentString(int indent, int tabSize) {
263: StringBuilder sb = new StringBuilder();
264: while (indent >= tabSize) {
265: sb.append('\t');
266: indent -= tabSize;
267: }
268: ArrayUtilities.appendSpaces(sb, indent);
269: return sb.toString();
270: }
271:
272: }
|