Index: indent/.cvsignore =================================================================== RCS file: indent/.cvsignore diff -N indent/.cvsignore --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/.cvsignore 5 Jun 2007 10:15:26 -0000 1.1.2.1 @@ -0,0 +1 @@ +build Index: indent/apichanges.xml =================================================================== RCS file: indent/apichanges.xml diff -N indent/apichanges.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/apichanges.xml 14 Jun 2007 12:37:08 -0000 1.1.2.1 @@ -0,0 +1,123 @@ + + + + + + + + + + + + General + + + + + + + + + editor/indent module was created. + + + + + + The module was created. + + + + + + + + + + + Change History of Editor Indentation API + + + + + + +

Introduction

+ +

This document lists changes made to the Editor Indent API.

+ + +
+ + +

@FOOTER@

+ + +
+ +
Index: indent/arch.xml =================================================================== RCS file: indent/arch.xml diff -N indent/arch.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/arch.xml 14 Jun 2007 12:37:08 -0000 1.1.2.2 @@ -0,0 +1,1167 @@ + + + +]> + + + + &api-questions; + + + + +

+ Editor Indentation module defines + + providing indentation and reformatting services to the clients. +
+ It also contains SPI allowing the modules to register language-specific + indenters and formatters into + MimeLookup + through XML layers. +

+
+ + + + + +

+ Unit tests are provided for checking correctness of infrastructure and delegation + from the legacy editor actions to the new API. +

+
+ + + + + +

+ Available for NetBeans 6.0. +

+
+ + + + + +

+API Usecases +

+ +

+Fix indentation of a single or multiple lines of a document. +

+

+Altghough there are formatting actions already there may be clients +wishing to explicitly fix indentation of e.g. a newly inserted code into a Swing document. +

+

+The same code is used after inserting a newline into a document. +

+The +Indent +is an entry point for performing reindentation. The following code should be used by clients: +
+Indent indent = Indent.get(doc);
+indent.lock();
+try {
+    doc.atomicLock();
+    try {
+        indent.reindent(startOffset, endOffset);
+    } finally {
+        doc.atomicUnlock();
+    }
+} finally {
+    indent.unlock();
+}
+
+ +

+Code beautification of a selected area of a document. +

+

+Code beautification should not only fix line indentation but it may also perform +extra changes to code according to formatting rules. For example add newlines + or additional whitespace or add/remove extra braces etc. +

+The +Reformat +class should be used: +
+Reformat reformat = Reformat.get(doc);
+reformat.lock();
+try {
+    doc.atomicLock();
+    try {
+        reformat.reformat(startOffset, endOffset);
+    } finally {
+        doc.atomicUnlock();
+    }
+} finally {
+    reformat.unlock();
+}
+
+
+ + + + + +

+ Editor indentation performs reindentation and code beautification of Swing document. +

+
+ + + + + + + + + + + + +

+ The new API should coexist with existing APIs mainly + IndentEngine + Formatter + but in the future we would like to deprecate the mentioned APIs. +

+
+ + + + + +

+ Yes. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ Yes. +

+
+ + + + + +

+ JRE 1.5. +

+
+ + + + + +

+ JRE is enough. +

+
+ + + + + + + + + + + + +

+ None. +

+
+ + + + + +

+ All platforms. +

+
+ + + + + +

+ Nothing. +

+
+ + + + + +

+ Jar only. +

+
+ + + + + +

+ Yes. +

+
+ + + + + +

+ Only API and SPI packages are public. +

+
+ + + + + +

+ Anywhere. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ Both reindentation and reformatting are executed synchronously in a thread that calls + the API classes (typically AWT thread). +

+
+ + + + + +

+ None. +

+
+ + + + + +

+ None. +

+
+ + + + + +

+ None. +

+
+ + + + + +

+ It uses MimeLookup to search for registered indentation and reformatting task factories. +

+
+ + + + + +

+ The module registers org.netbeans.modules.editor.lib.FormatterOverride + implementation so that it gets callback-ed from Editor Library formatter's infrastructure. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No significant memory consumption. Only line indent strings are reasonably cached. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ Reformatting or reindentation of a large block of text can be time-consuming. +
+ The API is however not tied to be called in AWT thread only so the caller may reschedule + the processing into another thread. It should ensure that the user won't be typing + (or otherwise modifying) the document being reformatted. +

+
+ + + + + +

+ Size of the code block being reformatted and complexity of the reformatters. +

+
+ + + + + +

+ The plugged in code should control its complexity. The indentation infrastructure + may provide a logger for displaying of the time spent in the reformatting + and reindenting tasks. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ Not directly. Only through MimeLookup. +

+
+ + + + + +

+ No. +

+
+ + + + + +

+ No. +

+
+ +
Index: indent/build.xml =================================================================== RCS file: indent/build.xml diff -N indent/build.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/build.xml 5 Jun 2007 10:15:26 -0000 1.1.2.1 @@ -0,0 +1,23 @@ + + + + + + Index: indent/manifest.mf =================================================================== RCS file: indent/manifest.mf diff -N indent/manifest.mf --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/manifest.mf 5 Jun 2007 10:15:25 -0000 1.1.2.1 @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +OpenIDE-Module: org.netbeans.modules.editor.indent/1 +OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/indent/Bundle.properties +OpenIDE-Module-Layer: org/netbeans/modules/editor/indent/resources/layer.xml Index: indent/nbproject/.cvsignore =================================================================== RCS file: indent/nbproject/.cvsignore diff -N indent/nbproject/.cvsignore --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/nbproject/.cvsignore 5 Jun 2007 10:15:30 -0000 1.1.2.1 @@ -0,0 +1 @@ +private Index: indent/nbproject/project.properties =================================================================== RCS file: indent/nbproject/project.properties diff -N indent/nbproject/project.properties --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/nbproject/project.properties 7 Jun 2007 14:54:01 -0000 1.1.2.2 @@ -0,0 +1,24 @@ +# The contents of this file are subject to the terms of the Common Development +# and Distribution License (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.html +# or http://www.netbeans.org/cddl.txt. +# +# When distributing Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://www.netbeans.org/cddl.txt. +# If applicable, add the following below the CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# +# 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. + +javac.source=1.5 +javac.compilerargs=-Xlint:unchecked +spec.version.base=1.0 + +javadoc.arch=${basedir}/arch.xml +javadoc.apichanges=${basedir}/apichanges.xml +javadoc.title=Editor Indentation Index: indent/nbproject/project.xml =================================================================== RCS file: indent/nbproject/project.xml diff -N indent/nbproject/project.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/nbproject/project.xml 7 Jun 2007 15:48:29 -0000 1.1.2.2 @@ -0,0 +1,98 @@ + + + + org.netbeans.modules.apisupport.project + + + org.netbeans.modules.editor.indent + + + org.netbeans.modules.editor.lib + + + + 1 + + + + + org.netbeans.modules.editor.mimelookup + + + + 1 + 1.5 + + + + org.netbeans.modules.editor.util + + + + 1 + 1.17 + + + + org.netbeans.modules.lexer + + + + 2 + 1.19 + + + + org.openide.util + + + + 7.9 + + + + + + unit + + org.netbeans.modules.editor.indent + + + + + org.netbeans.modules.editor + + + + org.netbeans.modules.editor.lib + + + + + qa-functional + + + + org.netbeans.api.editor.indent + org.netbeans.spi.editor.indent + + + + Index: indent/src/META-INF/services/org.netbeans.modules.editor.lib.FormatterOverride =================================================================== RCS file: indent/src/META-INF/services/org.netbeans.modules.editor.lib.FormatterOverride diff -N indent/src/META-INF/services/org.netbeans.modules.editor.lib.FormatterOverride --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/META-INF/services/org.netbeans.modules.editor.lib.FormatterOverride 7 Jun 2007 14:54:01 -0000 1.1.2.1 @@ -0,0 +1 @@ +org.netbeans.modules.editor.indent.FormatterOverrideImpl Index: indent/src/org/netbeans/api/editor/indent/Indent.java =================================================================== RCS file: indent/src/org/netbeans/api/editor/indent/Indent.java diff -N indent/src/org/netbeans/api/editor/indent/Indent.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/api/editor/indent/Indent.java 14 Jun 2007 08:06:01 -0000 1.1.2.3 @@ -0,0 +1,149 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.api.editor.indent; + +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.modules.editor.indent.IndentImpl; + +/** + * Reindentation of a single or multiple lines in the document + * means fixing of the line's indent only but does not do any other + * code beautification. + *
+ * The following pattern should be used: + *
+ * indent.lock();
+ * try {
+ *     doc.atomicLock();
+ *     try {
+ *         indent.reindent(...);
+ *     } finally {
+ *         doc.atomicUnlock();
+ *     }
+ * } finally {
+ *     indent.unlock();
+ * }
+ * 
+ * + * @author Miloslav Metelka + */ +public final class Indent { + + /** + * Get the indentation for the given document. + * + * @param doc non-null document. + * @return non-null indentation. + */ + public static Indent get(Document doc) { + IndentImpl indentImpl = IndentImpl.get(doc); + Indent indent = indentImpl.getIndent(); + if (indent == null) { + indent = new Indent(indentImpl); + indentImpl.setIndent(indent); + } + return indent; + } + + private final IndentImpl impl; + + private Indent(IndentImpl impl) { + this.impl = impl; + } + + /** + * Clients should call this method before acquiring of document's write lock. + *
+ * The following pattern should be used: + *
+     * indent.lock();
+     * try {
+     *     doc.atomicLock();
+     *     try {
+     *         indent.reindent(...);
+     *     } finally {
+     *         doc.atomicUnlock();
+     *     }
+     * } finally {
+     *     indent.unlock();
+     * }
+     * 
+ */ + public void lock() { + impl.indentLock(); + } + + /** + * Clients should call this method after releasing of document's write lock. + *
+ * The following pattern should be used: + *
+     * indent.lock();
+     * try {
+     *     doc.atomicLock();
+     *     try {
+     *         indent.reindent(...);
+     *     } finally {
+     *         doc.atomicUnlock();
+     *     }
+     * } finally {
+     *     indent.unlock();
+     * }
+     * 
+ */ + public void unlock() { + impl.indentUnlock(); + } + + /** + * Correct indentation on a single line determined by the given offset. + *
+ * Typically it is called after newline gets inserted + * or when a line is reindented explicitly (e.g. by pressing TAB key in emacs mode). + *
+ * This method will fallback to the editor formatting infrastructure + * in case there are no registered indent or reformat factories. + * + * @param offset >=0 any offset on the line to be reformatted. + * @throws BadLocationException in case the indenter attempted to insert/remove + * at an invalid offset or e.g. into a guarded section. + */ + public void reindent(int offset) throws BadLocationException { + reindent(offset, offset); + } + + /** + * Correct indentation of all lines in the given offset range. + *
+ * This method will fallback to the editor formatting infrastructure + * in case there are no registered indent or reformat factories. + * + * @param startOffset >=0 any offset on a first line to be reformatted. + * @param endOffset >=startOffset any offset (including end offset) + * on a last line to be reformatted. + * @throws BadLocationException in case the indenter attempted to insert/remove + * at an invalid offset or e.g. into a guarded section. + */ + public void reindent(int startOffset, int endOffset) throws BadLocationException { + impl.reindent(startOffset, endOffset); + } + +} Index: indent/src/org/netbeans/api/editor/indent/IndentUtils.java =================================================================== RCS file: indent/src/org/netbeans/api/editor/indent/IndentUtils.java diff -N indent/src/org/netbeans/api/editor/indent/IndentUtils.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/api/editor/indent/IndentUtils.java 14 Jun 2007 08:06:02 -0000 1.1.2.2 @@ -0,0 +1,240 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.api.editor.indent; + +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. + *
+ * 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][]; + + 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 { + 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 { + 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; + + default: + if (Character.isWhitespace(ch)) + indent++; + else + return indent; + } + } + return indent; + } + + /** + * Create (or get from cache) indentation string for the given indent. + *
+ * 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(); + } + + private static void checkOffsetInDocument(Document doc, int offset) throws BadLocationException { + if (offset < 0) + throw new BadLocationException("offset=" + offset + " < 0", offset); // NOI18N + if (offset > doc.getLength()) + throw new BadLocationException("offset=" + offset + " > doc.getLength()=" + doc.getLength(), offset); + } + +} Index: indent/src/org/netbeans/api/editor/indent/Reformat.java =================================================================== RCS file: indent/src/org/netbeans/api/editor/indent/Reformat.java diff -N indent/src/org/netbeans/api/editor/indent/Reformat.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/api/editor/indent/Reformat.java 14 Jun 2007 08:06:02 -0000 1.1.2.3 @@ -0,0 +1,132 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.api.editor.indent; + +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.modules.editor.indent.IndentImpl; + +/** + * Reformatting of a block of code in a document. + *
+ * The following pattern should be used: + *
+ * reformat.lock();
+ * try {
+ *     doc.atomicLock();
+ *     try {
+ *         reformat.reformat(...);
+ *     } finally {
+ *         doc.atomicUnlock();
+ *     }
+ * } finally {
+ *     reformat.unlock();
+ * }
+ * 
+ * + * @author Miloslav Metelka + */ +public final class Reformat { + + /** + * Get the reformatting for the given document. + * + * @param doc non-null document. + * @return non-null reformat object. + */ + public static Reformat get(Document doc) { + IndentImpl indentImpl = IndentImpl.get(doc); + Reformat reformat = indentImpl.getReformat(); + if (reformat == null) { + reformat = new Reformat(indentImpl); + indentImpl.setReformat(reformat); + } + return reformat; + } + + private final IndentImpl impl; + + private Reformat(IndentImpl impl) { + this.impl = impl; + } + + /** + * Clients should call this method before acquiring of document's write lock. + *
+ * The following pattern should be used: + *
+     * reformat.lock();
+     * try {
+     *     doc.atomicLock();
+     *     try {
+     *         reformat.reformat(...);
+     *     } finally {
+     *         doc.atomicUnlock();
+     *     }
+     * } finally {
+     *     reformat.unlock();
+     * }
+     * 
+ */ + public void lock() { + impl.reformatLock(); + } + + /** + * Clients should call this method after releasing of document's write lock. + *
+ * The following pattern should be used: + *
+     * reformat.lock();
+     * try {
+     *     doc.atomicLock();
+     *     try {
+     *         reformat.reformat(...);
+     *     } finally {
+     *         doc.atomicUnlock();
+     *     }
+     * } finally {
+     *     reformat.unlock();
+     * }
+     * 
+ */ + public void unlock() { + impl.reformatUnlock(); + } + + /** + * Reformat a given range of code in the given document. + *
+ * It includes possible fixing of indentation and possible code beautification + * dependent on the implementation of the reformatter. + * + *

+ * If the reformatter implementation is not available the reindentation + * will be performed (i.e. just the line indentation will be fixed) + * as a closest match. + * + * @param startOffset start offset of the area to be reformatted. + * @param endOffset end offset of the area to be reformatted. + */ + public void reformat(int startOffset, int endOffset) throws BadLocationException { + impl.reformat(startOffset, endOffset); + } + +} Index: indent/src/org/netbeans/modules/editor/indent/Bundle.properties =================================================================== RCS file: indent/src/org/netbeans/modules/editor/indent/Bundle.properties diff -N indent/src/org/netbeans/modules/editor/indent/Bundle.properties --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/modules/editor/indent/Bundle.properties 5 Jun 2007 10:15:28 -0000 1.1.2.1 @@ -0,0 +1,22 @@ +# The contents of this file are subject to the terms of the Common Development +# and Distribution License (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.html +# or http://www.netbeans.org/cddl.txt. +# +# When distributing Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://www.netbeans.org/cddl.txt. +# If applicable, add the following below the CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# +# 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. + +OpenIDE-Module-Name=Editor Indentation +OpenIDE-Module-Display-Category=Editing +OpenIDE-Module-Short-Description=Contains indentation APIs and SPIs. +OpenIDE-Module-Long-Description=The module includes indenation and code formatting APIs and infrastructure. + Index: indent/src/org/netbeans/modules/editor/indent/FormatterImpl.java =================================================================== RCS file: indent/src/org/netbeans/modules/editor/indent/FormatterImpl.java diff -N indent/src/org/netbeans/modules/editor/indent/FormatterImpl.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/modules/editor/indent/FormatterImpl.java 14 Jun 2007 08:06:02 -0000 1.1.2.2 @@ -0,0 +1,122 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.modules.editor.indent; + +import java.io.IOException; +import java.io.Writer; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import org.netbeans.editor.BaseDocument; +import org.netbeans.editor.Formatter; +import org.netbeans.editor.GuardedException; +import org.netbeans.editor.ext.ExtFormatter; +import org.netbeans.spi.editor.indent.Context; + +/** + * Indentation and code reformatting services for a swing text document. + * + * @author Miloslav Metelka + */ +public final class FormatterImpl extends ExtFormatter { + + private Formatter defaultFormatter; + + private IndentImpl indentImpl; + + FormatterImpl(Formatter defaultFormatter, Document doc) { + super(defaultFormatter.getKitClass()); + this.indentImpl = IndentImpl.get(doc); + } + + public int[] getReformatBlock(JTextComponent target, String typedText) { + return (defaultFormatter instanceof ExtFormatter) + ? ((ExtFormatter)defaultFormatter).getReformatBlock(target, typedText) + : null; + } + + public void indentLock() { + indentImpl.indentLock(); + } + + public void indentUnlock() { + indentImpl.indentUnlock(); + } + + public void reformatLock() { + indentImpl.reformatLock(); + } + + public void reformatUnlock() { + indentImpl.reformatUnlock(); + } + + public int indentLine(Document doc, int offset) { + try { + indentImpl.reindent(offset, offset); + return offset; + } catch (GuardedException e) { + java.awt.Toolkit.getDefaultToolkit().beep(); + } catch (BadLocationException e) { + throw new IllegalStateException(e); + } + 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++; + return indentLine(doc, offset); + } catch (GuardedException e) { + java.awt.Toolkit.getDefaultToolkit().beep(); + } catch (BadLocationException e) { + throw new IllegalStateException(e); + } + return offset; + } + + public Writer reformat(BaseDocument doc, int startOffset, int endOffset, + boolean indentOnly) throws BadLocationException, IOException { + // TBD delegate somehow + return (defaultFormatter instanceof ExtFormatter) + ? ((ExtFormatter)defaultFormatter).reformat(doc, startOffset, endOffset, indentOnly) + : null; + } + + public int reformat(BaseDocument doc, int startOffset, int endOffset) + throws BadLocationException { + if (doc != indentImpl.document()) + return endOffset - startOffset; // should not happen in reality + indentImpl.reformat(startOffset, endOffset); + Context ctx = indentImpl.reformatContext(); + return (ctx != null) + ? ctx.endOffset() - ctx.startOffset() + : endOffset - startOffset; + } + +} Index: indent/src/org/netbeans/modules/editor/indent/FormatterOverrideImpl.java =================================================================== RCS file: indent/src/org/netbeans/modules/editor/indent/FormatterOverrideImpl.java diff -N indent/src/org/netbeans/modules/editor/indent/FormatterOverrideImpl.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/modules/editor/indent/FormatterOverrideImpl.java 7 Jun 2007 14:54:00 -0000 1.1.2.1 @@ -0,0 +1,41 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.modules.editor.indent; + +import javax.swing.text.Document; +import org.netbeans.editor.Formatter; +import org.netbeans.modules.editor.lib.FormatterOverride; + +/** + * Indentation and code reformatting services for a swing text document. + * + * @author Miloslav Metelka + */ +public final class FormatterOverrideImpl implements FormatterOverride { + + public Formatter getFormatter(Document doc, Formatter defaultFormatter) { + IndentImpl indentImpl = IndentImpl.get(doc); + if (indentImpl.hasIndentOrReformatFactories()) { + defaultFormatter = new FormatterImpl(defaultFormatter, doc); + } + return defaultFormatter; + } + +} Index: indent/src/org/netbeans/modules/editor/indent/IndentImpl.java =================================================================== RCS file: indent/src/org/netbeans/modules/editor/indent/IndentImpl.java diff -N indent/src/org/netbeans/modules/editor/indent/IndentImpl.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/modules/editor/indent/IndentImpl.java 14 Jun 2007 08:06:02 -0000 1.1.2.3 @@ -0,0 +1,406 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.modules.editor.indent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.StyledDocument; +import org.netbeans.api.editor.indent.Indent; +import org.netbeans.api.editor.indent.Reformat; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.api.editor.mimelookup.MimePath; +import org.netbeans.api.lexer.LanguagePath; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.editor.BaseDocument; +import org.netbeans.spi.editor.indent.Context; +import org.netbeans.spi.editor.indent.ExtraLock; +import org.netbeans.spi.editor.indent.IndentTask; +import org.netbeans.spi.editor.indent.ReformatTask; +import org.openide.util.Lookup; + +/** + * Indentation and code reformatting services for a swing text document. + * + * @author Miloslav Metelka + */ +public final class IndentImpl { + + // -J-Dorg.netbeans.modules.editor.indent.IndentImpl=FINE + private static final Logger LOG = Logger.getLogger(IndentImpl.class.getName()); + + public static IndentImpl get(Document doc) { + IndentImpl indentImpl = (IndentImpl)doc.getProperty(IndentImpl.class); + if (indentImpl == null) { + indentImpl = new IndentImpl(doc); + doc.putProperty(IndentImpl.class, indentImpl); + } + return indentImpl; + } + + private final Document doc; + + private Indent indent; + + private Reformat reformat; + + private TaskHandler indentHandler; + + private TaskHandler reformatHandler; + + public IndentImpl(Document doc) { + this.doc = doc; + } + + public Document document() { + return doc; + } + + public Indent getIndent() { + return indent; + } + + public void setIndent(Indent indent) { + this.indent = indent; + } + + public Reformat getReformat() { + return reformat; + } + + public void setReformat(Reformat reformat) { + this.reformat = reformat; + } + + public boolean hasIndentOrReformatFactories() { + return new TaskHandler(true, doc).hasFactories(); + } + + public synchronized void indentLock() { + if (indentHandler != null) + throw new IllegalStateException("Already locked"); + indentHandler = new TaskHandler(true, doc); + if (indentHandler.collectTasks()) { + indentHandler.lock(); + } + } + + public synchronized void indentUnlock() { + if (indentHandler == null) + throw new IllegalStateException("Already unlocked"); + indentHandler.unlock(); + indentHandler = null; + } + + public Context indentContext() { + return indentHandler.context(); + } + + public synchronized void reformatLock() { + if (reformatHandler != null) + throw new IllegalStateException("Already locked"); + reformatHandler = new TaskHandler(false, doc); + if (reformatHandler.collectTasks()) { + reformatHandler.lock(); + } + } + + public synchronized void reformatUnlock() { + if (reformatHandler == null) + throw new IllegalStateException("Already unlocked"); + reformatHandler.unlock(); + reformatHandler = null; + } + + public Context reformatContext() { + return reformatHandler.context(); + } + + public void reindent(int startOffset, int endOffset) throws BadLocationException { + assert (indentHandler != null) : "Not locked. Use Indent.lock()"; // NOI18N + assert (startOffset <= endOffset) : "startOffset=" + startOffset + " > endOffset=" + endOffset; // NOI18N + // Find begining of line + Element lineRootElem = lineRootElement(doc); + // Correct the start offset to point to the begining of the start line + int startLineIndex = lineRootElem.getElementIndex(startOffset); + if (startLineIndex < 0) + return; // Invalid line index => do nothing + Element lineElem = lineRootElem.getElement(startLineIndex); + int startLineOffset = lineElem.getStartOffset(); + boolean done = false; + if (indentHandler.hasItems()) { + // Find ending line element - by default use the same as for start offset + if (endOffset > lineElem.getEndOffset()) { // need to get a different line element + int endLineIndex = lineRootElem.getElementIndex(endOffset); + lineElem = lineRootElem.getElement(endLineIndex); + // Check if the given endOffset ends right after line's newline (in fact at the begining of the next line) + if (endLineIndex > 0 && lineElem.getStartOffset() == endOffset) { + endLineIndex--; + lineElem = lineRootElem.getElement(endLineIndex); + } + } + + // Create context from begining of the start line till the end of the end line. + IndentSpiPackageAccessor.get().resetBounds( + indentHandler.context(), + doc.createPosition(startLineOffset), + doc.createPosition(lineElem.getEndOffset())); + + // Perform whole reindent on top and possibly embedded levels + indentHandler.runTasks(); + done = true; + } + + // Fallback to Formatter + if (!done && doc instanceof BaseDocument) { + // Original formatter does not have reindentation of multiple lines + // so reformat start line and continue for each line. + do { + ((BaseDocument)doc).getFormatter().indentLine(doc, startOffset); + startOffset = lineElem.getEndOffset(); // Move to next line + } while (startOffset < endOffset); + + } + } + + public void reformat(int startOffset, int endOffset) throws BadLocationException { + assert (reformatHandler != null) : "Not locked. Use Reformat.lock()"; // NOI18N + assert (startOffset <= endOffset) : "startOffset=" + startOffset + " > endOffset=" + endOffset; // NOI18N + boolean done = false; + if (reformatHandler.hasItems()) { + IndentSpiPackageAccessor.get().resetBounds( + reformatHandler.context(), + doc.createPosition(startOffset), + doc.createPosition(endOffset)); + + // Run top and embedded reformatting + reformatHandler.runTasks(); + + // Perform reformatting of the top section and possible embedded sections + done = true; + } + + // Fallback to Formatter + if (!done && doc instanceof BaseDocument) { + BaseDocument bdoc = (BaseDocument)doc; + bdoc.getFormatter().reformat(bdoc, startOffset, endOffset); + } + } + + public static Element lineRootElement(Document doc) { + return (doc instanceof StyledDocument) + ? ((StyledDocument)doc).getParagraphElement(0).getParentElement() + : doc.getDefaultRootElement(); + } + + private static final class TaskHandler { + + private boolean indent; + + private Context context; + + private List items; + + private Map mime2Item; + + private int maxMimePathSize; + + TaskHandler(boolean indent, Document doc) { + this.indent = indent; + context = IndentSpiPackageAccessor.get().createContext(indent, doc); + } + + boolean isIndent() { + return indent; + } + + Context context() { + return context; + } + + Document document() { + return context.document(); + } + + int maxMimePathSize() { + return maxMimePathSize; + } + + boolean collectTasks() { + String mimeType = docMimeType(); + if (mimeType != null) { + // Get base indent task for the document. + // Only if it exists also get the ones for possible embedded sections. + MimePath mimePath = MimePath.get(mimeType); + if (addItem(mimePath)) { + // Also add the embedded ones + TokenHierarchy hi = TokenHierarchy.get(document()); + if (hi != null) { + Set languagePaths = hi.languagePaths(); + for (LanguagePath lp : languagePaths) { + mimePath = MimePath.parse(lp.mimePath()); + addItem(mimePath); + } + } + } + } + return (items != null); + } + + void lock() { + for (MimeItem item : items) { + item.lock(); + } + } + + void unlock() { + for (MimeItem item : items) { + item.unlock(); + } + } + + boolean hasFactories() { + String mimeType = docMimeType(); + return (mimeType != null && new MimeItem(this, MimePath.get(mimeType)).hasFactories()); + } + + boolean hasItems() { + return (items != null); + } + + void runTasks() throws BadLocationException { + // Run top-level task and possibly embedded tasks according to the context + if (items == null) // Do nothing for no items + return; + + // Start with the doc's mime type's task + item(0).runTask(); + + // Continue by reformatting the embedded sections from top-level to embedded + int mimePathSize = 2; + while (mimePathSize < maxMimePathSize) { + // TBD walk the top-level token sequence and go into it + mimePathSize++; + } + + } + + private boolean addItem(MimePath mimePath) { + maxMimePathSize = Math.max(maxMimePathSize, mimePath.size()); + MimeItem item = new MimeItem(this, mimePath); + if (item.createTask()) { + if (items == null) { + items = new ArrayList(); + mime2Item = new HashMap(); + } + if (!mime2Item.containsKey(item.mimePath())) { + items.add(item); + mime2Item.put(item.mimePath(), item); + } + return true; + } + return false; + } + + private MimeItem item(int index) { + return items.get(index); + } + + private String docMimeType() { + return (String)document().getProperty("mimeType"); + } + + } + + /** + * Item that services indentation/reformatting for a single mime-path. + */ + private static final class MimeItem { + + private final TaskHandler handler; + + private final MimePath mimePath; + + private IndentTask indentTask; + + private ReformatTask reformatTask; + + private ExtraLock extraLock; + + MimeItem(TaskHandler handler, MimePath mimePath) { + this.handler = handler; + this.mimePath = mimePath; + } + + MimePath mimePath() { + return mimePath; + } + + boolean hasFactories() { + Lookup lookup = MimeLookup.getLookup(mimePath); + return (lookup.lookup(IndentTask.Factory.class) != null) + || (lookup.lookup(ReformatTask.Factory.class) != null); + } + + boolean createTask() { + Lookup lookup = MimeLookup.getLookup(mimePath); + if (!handler.isIndent()) { // Attempt reformat task first + ReformatTask.Factory factory = lookup.lookup(ReformatTask.Factory.class); + if (factory != null && (reformatTask = factory.createTask(handler.context())) != null) { + extraLock = reformatTask.reformatLock(); + return true; + } + } + + if (handler.isIndent() || reformatTask == null) { // Possibly fallback to reindent for reformatting + IndentTask.Factory factory = lookup.lookup(IndentTask.Factory.class); + if (factory != null && (indentTask = factory.createTask(handler.context())) != null) { + extraLock = indentTask.indentLock(); + return true; + } + } + return false; + } + + void lock() { + extraLock.lock(); + } + + void runTask() throws BadLocationException { + if (indentTask != null) { + indentTask.reindent(); + } else { + reformatTask.reformat(); + } + } + + void unlock() { + extraLock.unlock(); + } + + } + +} Index: indent/src/org/netbeans/modules/editor/indent/IndentSpiPackageAccessor.java =================================================================== RCS file: indent/src/org/netbeans/modules/editor/indent/IndentSpiPackageAccessor.java diff -N indent/src/org/netbeans/modules/editor/indent/IndentSpiPackageAccessor.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/modules/editor/indent/IndentSpiPackageAccessor.java 5 Jun 2007 10:15:28 -0000 1.1.2.1 @@ -0,0 +1,64 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.modules.editor.indent; + +import javax.swing.text.Document; +import javax.swing.text.Position; +import org.netbeans.spi.editor.indent.Context; + +/** + * Accessor for the package-private functionality of bookmarks API. + * + * @author Miloslav Metelka + * @version 1.00 + */ + +public abstract class IndentSpiPackageAccessor { + + private static IndentSpiPackageAccessor INSTANCE; + + public static IndentSpiPackageAccessor get() { + if (INSTANCE == null) { + // Enforce the static initializer in Context class to be run + try { + Class.forName(Context.class.getName(), true, Context.class.getClassLoader()); + } catch (ClassNotFoundException e) { } + } + return INSTANCE; + } + + /** + * Register the accessor. The method can only be called once + * - othewise it throws IllegalStateException. + * + * @param accessor instance. + */ + public static void register(IndentSpiPackageAccessor accessor) { + if (INSTANCE != null) { + throw new IllegalStateException("Already registered"); // NOI18N + } + INSTANCE = accessor; + } + + public abstract Context createContext(boolean indent, Document doc); + + public abstract void resetBounds(Context ctx, Position startPos, Position endPos); + +} Index: indent/src/org/netbeans/modules/editor/indent/resources/layer.xml =================================================================== RCS file: indent/src/org/netbeans/modules/editor/indent/resources/layer.xml diff -N indent/src/org/netbeans/modules/editor/indent/resources/layer.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/modules/editor/indent/resources/layer.xml 5 Jun 2007 10:15:25 -0000 1.1.2.1 @@ -0,0 +1,26 @@ + + + + + + + + + Index: indent/src/org/netbeans/spi/editor/indent/Context.java =================================================================== RCS file: indent/src/org/netbeans/spi/editor/indent/Context.java diff -N indent/src/org/netbeans/spi/editor/indent/Context.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/spi/editor/indent/Context.java 14 Jun 2007 08:06:03 -0000 1.1.2.2 @@ -0,0 +1,123 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.spi.editor.indent; + +import javax.swing.text.Document; +import javax.swing.text.Position; +import org.netbeans.modules.editor.indent.IndentSpiPackageAccessor; + + +/** + * Context information for both indentation and reformatting. + *
+ * A common class allows to conveniently share code between indentation and reformatting. + * {@link #isIndent()} allows to check whether the actual processing + * is indentation or reformatting. + * + * @author Miloslav Metelka + */ + +public final class Context { + + static { + IndentSpiPackageAccessor.register(new PackageAccessor()); + } + + private final boolean indent; + + private final Document doc; + + private Position startPos; + + private Position endPos; + + Context(boolean indent, Document doc) { + this.indent = indent; + this.doc = doc; + this.startPos = startPos; + this.endPos = endPos; + } + + /** + * Document for which the reformatting or indenting is being done. + */ + public Document document() { + return doc; + } + + /** + * Starting offset of the area to be reformatted or reindented. + *
+ * The value gets updated accordingly when the reformatter performs modifications + * in the affected area. + */ + public int startOffset() { + return (startPos != null) ? startPos.getOffset() : -1; + } + + /** + * Starting offset of the area to be reformatted or reindented. + *
+ * The value gets updated accordingly when the reformatter performs modifications + * in the affected area. + */ + public int endOffset() { + return (endPos != null) ? endPos.getOffset() : -1; + } + + /** + * Modify indent of the line at the offset passed as the parameter. + */ + public void modifyIndent(int offset) { + // TBD + } + + /* + * Check whether the actual processing + * is indentation or reformatting. + * + *
+ * Indent tasks may be used for reformatting in case a reformat task + * is not available (for the given mimepath) but the indent task is available. + * + * @return true if indentation is being done or false for reformatting. + */ + public boolean isIndent() { + return indent; + } + + void resetBounds(Position startPos, Position endPos) { + this.startPos = startPos; + this.endPos = endPos; + } + + private static final class PackageAccessor extends IndentSpiPackageAccessor { + + public Context createContext(boolean indent, Document doc) { + return new Context(indent, doc); + } + + public void resetBounds(Context ctx, Position startPos, Position endPos) { + ctx.resetBounds(startPos, endPos); + } + + } + +} Index: indent/src/org/netbeans/spi/editor/indent/ExtraLock.java =================================================================== RCS file: indent/src/org/netbeans/spi/editor/indent/ExtraLock.java diff -N indent/src/org/netbeans/spi/editor/indent/ExtraLock.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/spi/editor/indent/ExtraLock.java 14 Jun 2007 13:12:46 -0000 1.1.2.2 @@ -0,0 +1,53 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.spi.editor.indent; + +/** + * Extra locking may be necessary for indentation/reformatting + * before the document gets write-locked and the actual + * indentation/reformatting gets started. + *
+ * For example java infrastructure requires this. + *
+ * The infrastructure guarantees this processing: + *

+ *   extraLock.lock();
+ *   try {
+ *       doc.atomicLock(); // either BaseDocument or e.g. NbDocument.runAtomic()
+ *       try {
+ *           // indent or reformat ...
+ *       } finally {
+ *           doc.atomicUnlock();
+ *       }
+ *   } finally {
+ *       extraLock.unlock();
+ *   }
+ * 
+ * + * @author Miloslav Metelka + */ + +public interface ExtraLock { + + void lock(); + + void unlock(); + +} Index: indent/src/org/netbeans/spi/editor/indent/IndentTask.java =================================================================== RCS file: indent/src/org/netbeans/spi/editor/indent/IndentTask.java diff -N indent/src/org/netbeans/spi/editor/indent/IndentTask.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/spi/editor/indent/IndentTask.java 14 Jun 2007 13:12:46 -0000 1.1.2.2 @@ -0,0 +1,74 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.spi.editor.indent; + +import javax.swing.text.BadLocationException; + +/** + * Indent task performs indentation on a single or multiple lines. + *
+ * Typically it is used to fix indentation after newline was inserted + * or to fix indentation for a selected block of code. + * + * @author Miloslav Metelka + */ + +public interface IndentTask { + + /** + * Perform reindentation of the line(s) of {@link Context#getDocument()} + * between {@link Context#startOffset()} and {@link Context#endOffset()}. + *
+ * It is called from AWT thread and it should process synchronously. It is used + * after a newline is inserted after the user presses Enter + * or when a current line must be reindented e.g. when Tab is pressed in emacs mode. + *
+ * The method should use information from the context and modify + * indentation at the given offset in the document. + * + * @throws BadLocationException in case the indent task attempted to insert/remove + * at an invalid offset or e.g. into a guarded section. + */ + void reindent() throws BadLocationException; + + /** + * Get an extra locking or null if no extra locking is necessary. + */ + ExtraLock indentLock(); + + /** + * Indent task factory produces indent tasks for the given context. + *
+ * It should be registered in MimeLookup via xml layer in "/Editors/<mime-type>" + * folder. + */ + public interface Factory { + + /** + * Create indenting task. + * + * @param context non-null indentation context. + * @return indenting task or null if the factory cannot handle the given context. + */ + IndentTask createTask(Context context); + + } + +} Index: indent/src/org/netbeans/spi/editor/indent/ReformatTask.java =================================================================== RCS file: indent/src/org/netbeans/spi/editor/indent/ReformatTask.java diff -N indent/src/org/netbeans/spi/editor/indent/ReformatTask.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/src/org/netbeans/spi/editor/indent/ReformatTask.java 14 Jun 2007 13:12:46 -0000 1.1.2.2 @@ -0,0 +1,74 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.spi.editor.indent; + +import javax.swing.text.BadLocationException; + +/** + * Reformat task performs actual reformatting within offset bounds of the given context. + * + * @author Miloslav Metelka + */ + +public interface ReformatTask { + + /** + * Perform reformatting of the {@link Context#getDocument()} + * between {@link Context#startOffset()} and {@link Context#endOffset()}. + *
+ * This method may be called several times repetitively for different areas + * of a reformatted area. + *
+ * It is called from AWT thread and it should process synchronously. It is used + * after a newline is inserted after the user presses Enter + * or when a current line must be reindented e.g. when Tab is pressed in emacs mode. + *
+ * The method should use information from the context and modify + * indentation at the given offset in the document. + * + * @throws BadLocationException in case the formatter attempted to insert/remove + * at an invalid offset or e.g. into a guarded section. + */ + void reformat() throws BadLocationException; + + /** + * Get an extra locking or null if no extra locking is necessary. + */ + ExtraLock reformatLock(); + + /** + * Reformat task factory produces reformat tasks for the given context. + *
+ * It should be registered in MimeLookup via xml layer in "/Editors/<mime-type>" + * folder. + */ + public interface Factory { + + /** + * Create reformatting task. + * + * @param context non-null indentation context. + * @return reformatting task or null if the factory cannot handle the given context. + */ + ReformatTask createTask(Context context); + + } + +} Index: indent/test/.cvsignore =================================================================== RCS file: indent/test/.cvsignore diff -N indent/test/.cvsignore --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/test/.cvsignore 5 Jun 2007 10:15:24 -0000 1.1.2.1 @@ -0,0 +1,3 @@ +lib +results +work Index: indent/test/build.xml =================================================================== RCS file: indent/test/build.xml diff -N indent/test/build.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/test/build.xml 5 Jun 2007 10:15:24 -0000 1.1.2.1 @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: indent/test/cfg-unit.xml =================================================================== RCS file: indent/test/cfg-unit.xml diff -N indent/test/cfg-unit.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/test/cfg-unit.xml 5 Jun 2007 10:15:24 -0000 1.1.2.1 @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: indent/test/unit/src/META-INF/services/org.netbeans.spi.editor.mimelookup.MimeDataProvider =================================================================== RCS file: indent/test/unit/src/META-INF/services/org.netbeans.spi.editor.mimelookup.MimeDataProvider diff -N indent/test/unit/src/META-INF/services/org.netbeans.spi.editor.mimelookup.MimeDataProvider --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/test/unit/src/META-INF/services/org.netbeans.spi.editor.mimelookup.MimeDataProvider 5 Jun 2007 10:15:31 -0000 1.1.2.1 @@ -0,0 +1 @@ +org.netbeans.modules.editor.indent.IndentTestMimeDataProvider Index: indent/test/unit/src/org/netbeans/api/editor/indent/IndentTest.java =================================================================== RCS file: indent/test/unit/src/org/netbeans/api/editor/indent/IndentTest.java diff -N indent/test/unit/src/org/netbeans/api/editor/indent/IndentTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/test/unit/src/org/netbeans/api/editor/indent/IndentTest.java 5 Jun 2007 10:15:31 -0000 1.1.2.1 @@ -0,0 +1,118 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.api.editor.indent; + +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.PlainDocument; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.editor.indent.IndentTestMimeDataProvider; +import org.netbeans.spi.editor.indent.Context; +import org.netbeans.spi.editor.indent.ExtraLock; +import org.netbeans.spi.editor.indent.IndentTask; + +/** + * + * @author Miloslav Metelka + */ +public class IndentTest extends NbTestCase { + + private static final String MIME_TYPE = "text/x-test"; + + public IndentTest(String name) { + super(name); + } + + public void testFindIndentTaskFactory() throws BadLocationException { + TestIndentTask.TestFactory factory = new TestIndentTask.TestFactory(); + IndentTestMimeDataProvider.addInstances(MIME_TYPE, factory); + Document doc = new PlainDocument(); + doc.putProperty("mimeType", MIME_TYPE); + Indent indent = Indent.get(doc); + indent.lock(); + try { + //doc.atomicLock(); + try { + indent.reindent(0); + } finally { + //doc.atomicUnlock(); + } + } finally { + indent.unlock(); + } + // Check that the factory was used + assertTrue(factory.lastCreatedTask.indentPerformed); + } + + private static final class TestIndentTask implements IndentTask { + + private Context context; + + TestExtraLocking lastCreatedLocking; + + boolean indentPerformed; + + TestIndentTask(Context context) { + this.context = context; + } + + public void reindent() throws BadLocationException { + assertTrue(lastCreatedLocking.locked); + context.document().insertString(0, " ", null); + indentPerformed = true; + } + + public ExtraLock indentLock() { + return (lastCreatedLocking = new TestExtraLocking()); + } + + static final class TestFactory implements IndentTask.Factory { + + static TestIndentTask lastCreatedTask; + + public IndentTask createTask(Context context) { + return (lastCreatedTask = new TestIndentTask(context)); + } + + } + + } + + private static final class TestExtraLocking implements ExtraLock { + + Boolean locked; + + public Boolean locked() { + return locked; + } + + public void lock() { + if (locked != null) + assertFalse(locked); + locked = true; + } + + public void unlock() { + assertTrue(locked); + locked = false; + } + + } +} Index: indent/test/unit/src/org/netbeans/api/editor/indent/IndentUtilsTest.java =================================================================== RCS file: indent/test/unit/src/org/netbeans/api/editor/indent/IndentUtilsTest.java diff -N indent/test/unit/src/org/netbeans/api/editor/indent/IndentUtilsTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/test/unit/src/org/netbeans/api/editor/indent/IndentUtilsTest.java 14 Jun 2007 08:06:03 -0000 1.1.2.2 @@ -0,0 +1,80 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.api.editor.indent; + +import org.netbeans.junit.NbTestCase; +import org.netbeans.lib.editor.util.ArrayUtilities; + +/** + * + * @author Miloslav Metelka + */ +public class IndentUtilsTest extends NbTestCase { + + private static final String MIME_TYPE = "text/x-test"; + + public IndentUtilsTest(String name) { + super(name); + } + + public void testIndentUtils() throws Exception { + // Test empty indent + assertSame("", IndentUtils.cachedOrCreatedIndentString(0, true, 8)); + assertSame("", IndentUtils.cachedOrCreatedIndentString(0, false, 4)); + + // Test tabSize + assertEquals("\t ", s = IndentUtils.cachedOrCreatedIndentString(6, false, 4)); + assertSame(s, IndentUtils.cachedOrCreatedIndentString(6, false, 4)); + assertEquals("\t\t\t ", s = IndentUtils.cachedOrCreatedIndentString(15, false, 4)); + assertSame(s, IndentUtils.cachedOrCreatedIndentString(15, false, 4)); + + // Test spaces-only + assertEquals(" ", s = IndentUtils.cachedOrCreatedIndentString(10, true, 4)); + assertEquals(s, IndentUtils.cachedOrCreatedIndentString(10, true, 4)); + + // Test many (non-cached) spaces + int testUncachedIndent = 90; + StringBuilder sb = new StringBuilder(testUncachedIndent); + ArrayUtilities.appendSpaces(sb, testUncachedIndent); + assertEquals(sb.toString(), IndentUtils.cachedOrCreatedIndentString(testUncachedIndent, true, 4)); + + // Test long (non-cached) tab indent + int i = testUncachedIndent; + sb.setLength(0); + while (i >= 8) { + sb.append('\t'); + i -= 8; + } + ArrayUtilities.appendSpaces(sb, i); + assertEquals(sb.toString(), IndentUtils.cachedOrCreatedIndentString(testUncachedIndent, false, 8)); + + } + +} Index: indent/test/unit/src/org/netbeans/modules/editor/indent/IndentActionsTest.java =================================================================== RCS file: indent/test/unit/src/org/netbeans/modules/editor/indent/IndentActionsTest.java diff -N indent/test/unit/src/org/netbeans/modules/editor/indent/IndentActionsTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/test/unit/src/org/netbeans/modules/editor/indent/IndentActionsTest.java 7 Jun 2007 15:48:28 -0000 1.1.2.1 @@ -0,0 +1,146 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.modules.editor.indent; + +import java.awt.event.ActionEvent; +import javax.swing.Action; +import javax.swing.JEditorPane; +import javax.swing.SwingUtilities; +import org.netbeans.api.editor.indent.*; +import javax.swing.text.BadLocationException; +import org.netbeans.editor.BaseKit; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.editor.NbEditorKit; +import org.netbeans.modules.editor.indent.IndentTestMimeDataProvider; +import org.netbeans.spi.editor.indent.Context; +import org.netbeans.spi.editor.indent.ExtraLock; +import org.netbeans.spi.editor.indent.IndentTask; + +/** + * + * @author Miloslav Metelka + */ +public class IndentActionsTest extends NbTestCase { + + private static final String MIME_TYPE = "text/x-test-actions"; + + public IndentActionsTest(String name) { + super(name); + } + + public void testIndentActions() { + // Must run in AWT thread (BaseKit.install() checks for that) + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater( + new Runnable() { + public void run() { + testIndentActions(); + } + } + ); + return; + } + + assertTrue(SwingUtilities.isEventDispatchThread()); + TestIndentTask.TestFactory factory = new TestIndentTask.TestFactory(); + IndentTestMimeDataProvider.addInstances(MIME_TYPE, factory); + TestKit kit = new TestKit(); + JEditorPane pane = new JEditorPane(); + pane.setEditorKit(kit); + assertEquals(MIME_TYPE, pane.getDocument().getProperty("mimeType")); + //doc.putProperty("mimeType", MIME_TYPE); + + // Test insert new line action + Action a = kit.getActionByName(BaseKit.insertBreakAction); + assertNotNull(a); + a.actionPerformed(new ActionEvent(pane, 0, "")); + // Check that the factory was used + assertTrue(factory.lastCreatedTask.indentPerformed); + + // Test reformat action + a = kit.getActionByName(BaseKit.formatAction); + assertNotNull(a); + a.actionPerformed(new ActionEvent(pane, 0, "")); + + } + + private static final class TestIndentTask implements IndentTask { + + private Context context; + + TestExtraLocking lastCreatedLocking; + + boolean indentPerformed; + + TestIndentTask(Context context) { + this.context = context; + } + + public void reindent() throws BadLocationException { + assertTrue(lastCreatedLocking.locked); + context.document().insertString(0, " ", null); + indentPerformed = true; + } + + public ExtraLock indentLock() { + return (lastCreatedLocking = new TestExtraLocking()); + } + + static final class TestFactory implements IndentTask.Factory { + + static TestIndentTask lastCreatedTask; + + public IndentTask createTask(Context context) { + return (lastCreatedTask = new TestIndentTask(context)); + } + + } + + } + + private static final class TestExtraLocking implements ExtraLock { + + Boolean locked; + + public Boolean locked() { + return locked; + } + + public void lock() { + if (locked != null) + assertFalse(locked); + locked = true; + } + + public void unlock() { + assertTrue(locked); + locked = false; + } + + } + + private static final class TestKit extends NbEditorKit { + + public String getContentType() { + return MIME_TYPE; + } + + } +} Index: indent/test/unit/src/org/netbeans/modules/editor/indent/IndentTestMimeDataProvider.java =================================================================== RCS file: indent/test/unit/src/org/netbeans/modules/editor/indent/IndentTestMimeDataProvider.java diff -N indent/test/unit/src/org/netbeans/modules/editor/indent/IndentTestMimeDataProvider.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ indent/test/unit/src/org/netbeans/modules/editor/indent/IndentTestMimeDataProvider.java 5 Jun 2007 10:15:29 -0000 1.1.2.1 @@ -0,0 +1,126 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.modules.editor.indent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import org.netbeans.api.editor.mimelookup.MimePath; +import org.netbeans.spi.editor.mimelookup.MimeDataProvider; +import org.openide.util.Lookup; +import org.openide.util.lookup.AbstractLookup; +import org.openide.util.lookup.InstanceContent; + +/** + * Testing mime data provider. + * + * @author vita + */ +public final class IndentTestMimeDataProvider implements MimeDataProvider { + + private static final HashMap CACHE = new HashMap(); + + /** Creates a new instance of MemoryMimeDataProvider */ + public IndentTestMimeDataProvider() { + } + + public Lookup getLookup(MimePath mimePath) { + return getLookup(mimePath.getPath(), true); + } + + public static void addInstances(String mimePath, Object... instances) { + assert mimePath != null : "Mime path can't be null"; + getLookup(mimePath, true).addInstances(instances); + } + + public static void removeInstances(String mimePath, Object... instances) { + assert mimePath != null : "Mime path can't be null"; + getLookup(mimePath, true).removeInstances(instances); + } + + public static void reset(String mimePath) { + if (mimePath == null) { + synchronized (CACHE) { + for(Lkp lookup : CACHE.values()) { + lookup.reset(); + } + } + } else { + Lkp lookup = getLookup(mimePath, false); + if (lookup != null) { + lookup.reset(); + } + } + } + + private static Lkp getLookup(String mimePath, boolean create) { + synchronized (CACHE) { + Lkp lookup = CACHE.get(mimePath); + if (lookup == null && create) { + lookup = new Lkp(); + CACHE.put(mimePath, lookup); + } + return lookup; + } + } + + private static final class Lkp extends AbstractLookup { + + private ArrayList all = new ArrayList(); + private InstanceContent contents; + + public Lkp() { + this(new InstanceContent()); + } + + private Lkp(InstanceContent ic) { + super(ic); + this.contents = ic; + } + + public void addInstances(Object... instances) { + all.addAll(Arrays.asList(instances)); + contents.set(all, null); + } + + public void removeInstances(Object... instances) { + ArrayList newAll = new ArrayList(); + + loop: + for(Object oo : all) { + for(Object o : instances) { + if (o == oo) { + continue loop; + } + } + + newAll.add(oo); + } + + all = newAll; + contents.set(all, null); + } + + public void reset() { + all.clear(); + contents.set(all, null); + } + } // End of Lkp class +} Index: lib/apichanges.xml =================================================================== RCS file: /cvs/editor/lib/apichanges.xml,v retrieving revision 1.7 retrieving revision 1.6.2.1 diff -u -r1.7 -r1.6.2.1 Index: lib/manifest.mf =================================================================== RCS file: /cvs/editor/lib/manifest.mf,v retrieving revision 1.14 retrieving revision 1.13.2.1 diff -u -r1.14 -r1.13.2.1 Index: lib/nbproject/project.properties =================================================================== RCS file: /cvs/editor/lib/nbproject/project.properties,v retrieving revision 1.19 retrieving revision 1.18.2.1 diff -u -r1.19 -r1.18.2.1 Index: libsrc/org/netbeans/editor/ActionFactory.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/ActionFactory.java,v retrieving revision 1.83 retrieving revision 1.83.6.2 diff -u -r1.83 -r1.83.6.2 --- libsrc/org/netbeans/editor/ActionFactory.java 5 Apr 2007 08:42:54 -0000 1.83 +++ libsrc/org/netbeans/editor/ActionFactory.java 7 Jun 2007 14:53:58 -0000 1.83.6.2 @@ -1483,48 +1483,54 @@ Cursor origCursor = target.getCursor(); target.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - doc.atomicLock(); + Formatter formatter = doc.getFormatter(); + formatter.reformatLock(); try { + doc.atomicLock(); + try { - int startPos; - Position endPosition; - if (caret.isSelectionVisible()) { - startPos = target.getSelectionStart(); - endPosition = doc.createPosition(target.getSelectionEnd()); - } else { - startPos = 0; - endPosition = doc.createPosition(doc.getLength()); - } + int startPos; + Position endPosition; + if (caret.isSelectionVisible()) { + startPos = target.getSelectionStart(); + endPosition = doc.createPosition(target.getSelectionEnd()); + } else { + startPos = 0; + endPosition = doc.createPosition(doc.getLength()); + } - int pos = startPos; - if (gdoc != null) { - pos = gdoc.getGuardedBlockChain().adjustToBlockEnd(pos); - } + int pos = startPos; + if (gdoc != null) { + pos = gdoc.getGuardedBlockChain().adjustToBlockEnd(pos); + } - while (pos < endPosition.getOffset()) { - int stopPos = endPosition.getOffset(); - if (gdoc != null) { // adjust to start of the next guarded block - stopPos = gdoc.getGuardedBlockChain().adjustToNextBlockStart(pos); - if (stopPos == -1 || stopPos > endPosition.getOffset()) { - stopPos = endPosition.getOffset(); + while (pos < endPosition.getOffset()) { + int stopPos = endPosition.getOffset(); + if (gdoc != null) { // adjust to start of the next guarded block + stopPos = gdoc.getGuardedBlockChain().adjustToNextBlockStart(pos); + if (stopPos == -1 || stopPos > endPosition.getOffset()) { + stopPos = endPosition.getOffset(); + } } - } - int reformattedLen = doc.getFormatter().reformat(doc, pos, stopPos); - pos = pos + reformattedLen; + int reformattedLen = formatter.reformat(doc, pos, stopPos); + pos = pos + reformattedLen; - if (gdoc != null) { // adjust to end of current block - pos = gdoc.getGuardedBlockChain().adjustToBlockEnd(pos); + if (gdoc != null) { // adjust to end of current block + pos = gdoc.getGuardedBlockChain().adjustToBlockEnd(pos); + } } - } - } catch (GuardedException e) { - target.getToolkit().beep(); - } catch (BadLocationException e) { - Utilities.annotateLoggable(e); + } catch (GuardedException e) { + target.getToolkit().beep(); + } catch (BadLocationException e) { + Utilities.annotateLoggable(e); + } finally { + doc.atomicUnlock(); + target.setCursor(origCursor); + } } finally { - doc.atomicUnlock(); - target.setCursor(origCursor); + formatter.reformatUnlock(); } } } Index: libsrc/org/netbeans/editor/BaseCaret.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/BaseCaret.java,v retrieving revision 1.132 retrieving revision 1.131.6.1 diff -u -r1.132 -r1.131.6.1 Index: libsrc/org/netbeans/editor/BaseDocument.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/BaseDocument.java,v retrieving revision 1.141 retrieving revision 1.141.2.1 diff -u -r1.141 -r1.141.2.1 --- libsrc/org/netbeans/editor/BaseDocument.java 27 Apr 2007 02:25:11 -0000 1.141 +++ libsrc/org/netbeans/editor/BaseDocument.java 5 Jun 2007 10:17:33 -0000 1.141.2.1 @@ -52,6 +52,8 @@ import javax.swing.undo.CannotUndoException; import javax.swing.undo.CannotRedoException; import org.netbeans.lib.editor.util.swing.DocumentListenerPriority; +import org.netbeans.modules.editor.lib.FormatterOverride; +import org.openide.util.Lookup; /** * Document implementation @@ -439,7 +441,11 @@ /** Get the formatter for this document. */ public Formatter getFormatter() { - return Formatter.getFormatter(kitClass); + Formatter f = (Formatter)getProperty("defaultFormatter"); + if (f == null) + f = Formatter.getFormatter(kitClass); + FormatterOverride fp = Lookup.getDefault().lookup(FormatterOverride.class); + return (fp != null) ? fp.getFormatter(this, f) : f; } public SyntaxSupport getSyntaxSupport() { Index: libsrc/org/netbeans/editor/BaseKit.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/BaseKit.java,v retrieving revision 1.167 retrieving revision 1.167.2.3 diff -u -r1.167 -r1.167.2.3 --- libsrc/org/netbeans/editor/BaseKit.java 5 May 2007 06:30:52 -0000 1.167 +++ libsrc/org/netbeans/editor/BaseKit.java 11 Jun 2007 15:09:12 -0000 1.167.2.3 @@ -1112,21 +1112,27 @@ } BaseDocument doc = (BaseDocument)target.getDocument(); - doc.atomicLock(); - DocumentUtilities.setTypingModification(doc, true); - try{ - target.replaceSelection(""); - Caret caret = target.getCaret(); - Object cookie = beforeBreak(target, doc, caret); + Formatter formatter = doc.getFormatter(); + formatter.indentLock(); + try { + doc.atomicLock(); + DocumentUtilities.setTypingModification(doc, true); + try { + target.replaceSelection(""); + Caret caret = target.getCaret(); + Object cookie = beforeBreak(target, doc, caret); - int dotPos = caret.getDot(); - int newDotPos = doc.getFormatter().indentNewLine(doc, dotPos); - caret.setDot(newDotPos); + int dotPos = caret.getDot(); + int newDotPos = doc.getFormatter().indentNewLine(doc, dotPos); + caret.setDot(newDotPos); - afterBreak(target, doc, caret, cookie); + afterBreak(target, doc, caret, cookie); + } finally { + DocumentUtilities.setTypingModification(doc, false); + doc.atomicUnlock(); + } } finally { - DocumentUtilities.setTypingModification(doc, false); - doc.atomicUnlock(); + formatter.indentUnlock(); } } } Index: libsrc/org/netbeans/editor/BasePosition.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/BasePosition.java,v retrieving revision 1.18 retrieving revision 1.17.22.1 diff -u -r1.18 -r1.17.22.1 Index: libsrc/org/netbeans/editor/BaseTextUI.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/BaseTextUI.java,v retrieving revision 1.87 retrieving revision 1.86.2.1 diff -u -r1.87 -r1.86.2.1 Index: libsrc/org/netbeans/editor/DocumentContent.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/DocumentContent.java,v retrieving revision 1.12 retrieving revision 1.11.6.1 diff -u -r1.12 -r1.11.6.1 Index: libsrc/org/netbeans/editor/EditorUI.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/EditorUI.java,v retrieving revision 1.93 retrieving revision 1.92.2.1 diff -u -r1.93 -r1.92.2.1 Index: libsrc/org/netbeans/editor/Formatter.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/Formatter.java,v retrieving revision 1.41 retrieving revision 1.41.14.2 diff -u -r1.41 -r1.41.14.2 --- libsrc/org/netbeans/editor/Formatter.java 16 Nov 2006 11:44:34 -0000 1.41 +++ libsrc/org/netbeans/editor/Formatter.java 7 Jun 2007 14:53:58 -0000 1.41.14.2 @@ -497,4 +497,113 @@ return writer; } + /** + * Formatter clients should call this method + * before acquiring of the document's write lock + * and using of the {@link #indentLine(Document,int)} + * and {@link #indentNewLine(Document,int)} methods. + *
+ * Subclasses may override this method + * and perform necessary pre-locking (e.g. of java infrastructure). + *
+ * The following pattern should be used: + *
+     * formatter.indentLock();
+     * try {
+     *     doc.atomicLock();
+     *     try {
+     *         formatter.indentLine(...);
+     *     } finally {
+     *         doc.atomicUnlock();
+     *     }
+     * } finally {
+     *     formatter.indentUnlock();
+     * }
+     * 
+ */ + public void indentLock() { + // No extra locking by default + } + + /** + * Formatter clients should call this method + * after releasing of the document's write lock + * as a counterpart of {@link #indentLock()}. + *
+ * Subclasses may override this method + * and perform necessary post-unlocking (e.g. of java infrastructure). + *
+ * The following pattern should be used: + *
+     * formatter.indentLock();
+     * try {
+     *     doc.atomicLock();
+     *     try {
+     *         formatter.indentLine(...);
+     *     } finally {
+     *         doc.atomicUnlock();
+     *     }
+     * } finally {
+     *     formatter.indentUnlock();
+     * }
+     * 
+ */ + public void indentUnlock() { + // No extra locking by default + } + + /** + * Formatter clients should call this method + * before acquiring of the document's write lock + * before using of {@link #reformat(BaseDocument,int,int)} method. + *
+ * Subclasses may override this method + * and perform necessary pre-locking (e.g. of java infrastructure). + *
+ * The following pattern should be used: + *
+     * formatter.reformatLock();
+     * try {
+     *     doc.atomicLock();
+     *     try {
+     *         formatter.reformat(...);
+     *     } finally {
+     *         doc.atomicUnlock();
+     *     }
+     * } finally {
+     *     formatter.reformatUnlock();
+     * }
+     * 
+ */ + public void reformatLock() { + // No extra locking by default + } + + /** + * Formatter clients should call this method + * after releasing of the document's write lock + * as a counterpart of {@link #reformatLock()}. + *
+ * Subclasses may override this method + * and perform necessary post-unlocking (e.g. of java infrastructure). + *
+ * The following pattern should be used: + *
+     * formatter.reformatLock();
+     * try {
+     *     doc.atomicLock();
+     *     try {
+     *         formatter.reformat(...);
+     *     } finally {
+     *         doc.atomicUnlock();
+     *     }
+     * } finally {
+     *     formatter.reformatUnlock();
+     * }
+     * 
+ */ + public void reformatUnlock() { + // No extra locking by default + } + } Index: libsrc/org/netbeans/editor/MarkVector.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/MarkVector.java,v retrieving revision 1.7 retrieving revision 1.6.10.1 diff -u -r1.7 -r1.6.10.1 Index: libsrc/org/netbeans/editor/MultiMark.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/MultiMark.java,v retrieving revision 1.6 retrieving revision 1.5.22.1 diff -u -r1.6 -r1.5.22.1 Index: libsrc/org/netbeans/editor/Registry.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/Registry.java,v retrieving revision 1.19 retrieving revision 1.18.10.1 diff -u -r1.19 -r1.18.10.1 Index: libsrc/org/netbeans/editor/ext/ExtCaret.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/ext/ExtCaret.java,v retrieving revision 1.51 retrieving revision 1.50.2.1 diff -u -r1.51 -r1.50.2.1 Index: libsrc/org/netbeans/editor/ext/ExtKit.java =================================================================== RCS file: /cvs/editor/libsrc/org/netbeans/editor/ext/ExtKit.java,v retrieving revision 1.74 retrieving revision 1.73.2.1 diff -u -r1.74 -r1.73.2.1 Index: libsrc/org/netbeans/modules/editor/lib/FormatterOverride.java =================================================================== RCS file: libsrc/org/netbeans/modules/editor/lib/FormatterOverride.java diff -N libsrc/org/netbeans/modules/editor/lib/FormatterOverride.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ libsrc/org/netbeans/modules/editor/lib/FormatterOverride.java 5 Jun 2007 10:17:32 -0000 1.1.2.1 @@ -0,0 +1,44 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.modules.editor.lib; + +import javax.swing.text.Document; +import org.netbeans.editor.Formatter; + +/** + * Class to be searched in lookup that can override a formatter for the given document. + *
+ * It is a private contract between editor/lib and editor/indent. + * + * @author Miloslav Metelka + */ +public interface FormatterOverride { + + /** + * Possibly override the default formatter used for the given document. + * + * @param doc non-null document for which the formatter is being searched. + * @param defaultFormatter default formatter found by the infrastructure + * or null if there is none. + * @return overriden formatter or the default formatter passed as the argument. + */ + Formatter getFormatter(Document doc, Formatter defaultFormatter); + +} Index: src/org/netbeans/modules/editor/NbEditorDocument.java =================================================================== RCS file: src/org/netbeans/modules/editor/NbEditorDocument.java diff -N src/org/netbeans/modules/editor/NbEditorDocument.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/netbeans/modules/editor/NbEditorDocument.java 7 Jun 2007 14:54:00 -0000 1.41.22.1 @@ -0,0 +1,394 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * 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. + */ + +package org.netbeans.modules.editor; + +import java.awt.Font; +import java.awt.Color; +import java.awt.Component; +import java.util.ArrayList; +import java.util.HashMap; +import java.text.AttributedCharacterIterator; +import javax.swing.text.AttributeSet; +import javax.swing.JEditorPane; +import org.netbeans.editor.BaseKit; +import org.netbeans.editor.GuardedDocument; +import org.netbeans.editor.PrintContainer; +import org.netbeans.editor.Formatter; +import org.netbeans.editor.Settings; +import org.netbeans.editor.SettingsChangeEvent; +import org.netbeans.editor.Utilities; +import org.openide.text.NbDocument; +import org.openide.text.AttributedCharacters; +import javax.swing.text.Position; +import org.openide.text.Annotation; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeEvent; +import java.util.Dictionary; +import java.util.Map; +import java.util.WeakHashMap; +import javax.swing.JToolBar; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.netbeans.editor.BaseDocument; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.editor.AnnotationDesc; + +/** +* BaseDocument extension managing the readonly blocks of text +* +* @author Miloslav Metelka +* @version 1.00 +*/ + +public class NbEditorDocument extends GuardedDocument +implements NbDocument.PositionBiasable, NbDocument.WriteLockable, +NbDocument.Printable, NbDocument.CustomEditor, NbDocument.CustomToolbar, NbDocument.Annotatable { + + /** Name of the formatter setting. */ + public static final String FORMATTER = "formatter"; // NOI18N + + /** Mime type of the document. The name of this property corresponds + * to the property that is filled in the document by CloneableEditorSupport. + */ + public static final String MIME_TYPE_PROP = "mimeType"; // NOI18N + + /** Indent engine for the given kitClass. */ + public static final String INDENT_ENGINE = "indentEngine"; // NOI18N + + /** Formatter being used. */ + private Formatter formatter; + + /** Map of [Annotation, AnnotationDesc] */ + private HashMap annoMap; + + // #39718 hotfix + private WeakHashMap annoBlackList; + + public NbEditorDocument(Class kitClass) { + super(kitClass); + addStyleToLayerMapping(NbDocument.BREAKPOINT_STYLE_NAME, + NbDocument.BREAKPOINT_STYLE_NAME + "Layer:10"); // NOI18N + addStyleToLayerMapping(NbDocument.ERROR_STYLE_NAME, + NbDocument.ERROR_STYLE_NAME + "Layer:20"); // NOI18N + addStyleToLayerMapping(NbDocument.CURRENT_STYLE_NAME, + NbDocument.CURRENT_STYLE_NAME + "Layer:30"); // NOI18N + setNormalStyleName(NbDocument.NORMAL_STYLE_NAME); + + annoMap = new HashMap(20); + annoBlackList = new WeakHashMap(); + } + + public void settingsChange(SettingsChangeEvent evt) { + super.settingsChange(evt); + + // Check whether the mimeType is set + Object o = getProperty(MIME_TYPE_PROP); + if (!(o instanceof String)) { + BaseKit kit = BaseKit.getKit(getKitClass()); + putProperty(MIME_TYPE_PROP, kit.getContentType()); + } + + // Fill in the indentEngine property + putProperty(INDENT_ENGINE, + new BaseDocument.PropertyEvaluator() { + + private Object cached; + + public Object getValue() { + if (cached == null) { + cached = Settings.getValue(getKitClass(), INDENT_ENGINE); + } + + return cached; + } + } + ); + + // Refresh formatter + formatter = null; + + } + + public void setCharacterAttributes(int offset, int length, AttributeSet s, + boolean replace) { + if (s != null) { + Object val = s.getAttribute(NbDocument.GUARDED); + if (val != null && val instanceof Boolean) { + if (((Boolean)val).booleanValue() == true) { // want make guarded + super.setCharacterAttributes(offset, length, guardedSet, replace); + } else { // want make unguarded + super.setCharacterAttributes(offset, length, unguardedSet, replace); + } + } else { // not special values, just pass + super.setCharacterAttributes(offset, length, s, replace); + } + } + } + + public java.text.AttributedCharacterIterator[] createPrintIterators() { + NbPrintContainer npc = new NbPrintContainer(); + print(npc); + return npc.getIterators(); + } + + public Component createEditor(JEditorPane j) { + return Utilities.getEditorUI(j).getExtComponent(); + } + + public JToolBar createToolbar(JEditorPane j) { + return Utilities.getEditorUI(j).getToolBarComponent(); + } + + public Formatter getFormatter() { + Formatter f = formatter; + if (f == null) { + formatter = (Formatter)Settings.getValue(getKitClass(), FORMATTER); + f = formatter; + } + putProperty("defaultFormatter", f); + // The super implementation will inspect "defaultFormatter" property + return super.getFormatter(); + } + + /** Add annotation to the document. For annotation of whole line + * the length parameter can be ignored (specify value -1). + * @param startPos position which represent begining + * of the annotated text + * @param length length of the annotated text. If -1 is specified + * the whole line will be annotated + * @param annotation annotation which is attached to this text */ + public void addAnnotation(Position startPos, int length, Annotation annotation) { + Integer count = (Integer)annoBlackList.get(annotation); + if (count != null) { + // #39718 hotfix - test whether the annotation was already removed; if so, just remove it from the black list and return + if (count.intValue() == -1) { + annoBlackList.remove(annotation); + return; + } else if (count.intValue() < -1) { + annoBlackList.put(annotation, new Integer(count.intValue() + 1)); + return; + } + } + // partial fix of #33165 - read-locking of the document added + // BTW should only be invoked in EQ - see NbDocument.addAnnotation() + readLock(); + try { + // Recreate annotation's position to make sure it's in this doc at a valid offset + int docLen = getLength(); + int offset = startPos.getOffset(); + offset = Math.min(offset, docLen); + try { + startPos = createPosition(offset); + } catch (BadLocationException e) { + startPos = null; // should never happen + } + + AnnotationDescDelegate a = (AnnotationDescDelegate)annoMap.get(annotation); + if (a != null) { // already added before + // #39718 hotfix - remove the original annotation descriptor and put the annotation on the black list + a.detachListeners(); + getAnnotations().removeAnnotation(a); + annoMap.remove(annotation); + annoBlackList.put(annotation, new Integer(count != null ? count.intValue() + 1 : 1)); + } + if (annotation.getAnnotationType() != null) { + a = new AnnotationDescDelegate(this, startPos, length, annotation); + annoMap.put(annotation, a); + getAnnotations().addAnnotation(a); + } + } finally { + readUnlock(); + } + } + + /** Removal of added annotation. + * @param annotation annotation which is going to be removed */ + public void removeAnnotation(Annotation annotation) { + if (annotation == null) { // issue 14803 + return; // can't do more as the rest of stacktrace is in openide and ant + } + + Integer count = (Integer)annoBlackList.get(annotation); + if (count != null) { + // #39718 hotfix - test whether the annotation was already removed; if so, just remove it from the black list and return + if (count.intValue() == 1) { + annoBlackList.remove(annotation); + return; + } else if (count.intValue() > 1) { + annoBlackList.put(annotation, new Integer(count.intValue() - 1)); + return; + } + } + // partial fix of #33165 - read-locking of the document added + // BTW should only be invoked in EQ - see NbDocument.removeAnnotation() + readLock(); + try { + if (annotation.getAnnotationType() != null) { + AnnotationDescDelegate a = (AnnotationDescDelegate)annoMap.get(annotation); + if (a == null) { // not added yet + // #39718 hotfix - put the annotation on the black list and return + annoBlackList.put(annotation, new Integer(count != null ? count.intValue() - 1 : -1)); + return; + } + a.detachListeners(); + getAnnotations().removeAnnotation(a); + annoMap.remove(annotation); + } + } finally { + readUnlock(); + } + } + + Map getAnnoMap(){ + return annoMap; + } + + void addStreamDescriptionChangeListener(ChangeListener l) { + listenerList.add(ChangeListener.class, l); + } + + void removeStreamDescriptionChangeListener(ChangeListener l) { + listenerList.remove(ChangeListener.class, l); + } + + private void fireStreamDescriptionChange() { + ChangeEvent evt = new ChangeEvent(this); + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == ChangeListener.class) { + ((ChangeListener)listeners[i + 1]).stateChanged(evt); + } + } + } + + protected Dictionary createDocumentProperties(Dictionary origDocumentProperties) { + return new LazyPropertyMap(origDocumentProperties) { + public Object put(Object key, Object value) { + Object origValue = super.put(key, value); + if (Document.StreamDescriptionProperty.equals(key)) { + if (origValue == null || !origValue.equals(value)) { + fireStreamDescriptionChange(); + } + } + + return origValue; + } + }; + } + + /** Implementation of AnnotationDesc, which delegate to Annotation instance + * defined in org.openide.text package. + */ + static class AnnotationDescDelegate extends AnnotationDesc { + + private Annotation delegate; + private PropertyChangeListener l; + private Position pos; + private BaseDocument doc; + + AnnotationDescDelegate(BaseDocument doc, Position pos, int length, Annotation anno) { + super(pos.getOffset(),length); + this.pos = pos; + this.delegate = anno; + this.doc = doc; + + // update AnnotationDesc.type member + updateAnnotationType(); + + // forward property changes to AnnotationDesc property changes + l = new PropertyChangeListener() { + public void propertyChange (PropertyChangeEvent evt) { + if (evt.getPropertyName() == Annotation.PROP_SHORT_DESCRIPTION) + firePropertyChange(AnnotationDesc.PROP_SHORT_DESCRIPTION, null, null); + if (evt.getPropertyName() == Annotation.PROP_MOVE_TO_FRONT) + firePropertyChange(AnnotationDesc.PROP_MOVE_TO_FRONT, null, null); + if (evt.getPropertyName() == Annotation.PROP_ANNOTATION_TYPE) { + updateAnnotationType(); + firePropertyChange(AnnotationDesc.PROP_ANNOTATION_TYPE, null, null); + } + } + }; + delegate.addPropertyChangeListener(l); + } + + public String getAnnotationType() { + return delegate.getAnnotationType(); + } + + public String getShortDescription() { + return delegate.getShortDescription(); + } + + void detachListeners() { + delegate.removePropertyChangeListener(l); + } + + public int getOffset() { + return pos.getOffset(); + } + + public int getLine() { + try { + return Utilities.getLineOffset(doc, pos.getOffset()); + } catch (BadLocationException e) { + return 0; + } + } + + } + + + class NbPrintContainer extends AttributedCharacters implements PrintContainer { + + ArrayList acl = new ArrayList(); + + AttributedCharacters a; + + NbPrintContainer() { + a = new AttributedCharacters(); + } + + public void add(char[] chars, Font font, Color foreColor, Color backColor) { + a.append(chars, font, foreColor); + } + + public void eol() { + acl.add(a); + a = new AttributedCharacters(); + } + + public boolean initEmptyLines() { + return true; + } + + public AttributedCharacterIterator[] getIterators() { + int cnt = acl.size(); + AttributedCharacterIterator[] acis = new AttributedCharacterIterator[cnt]; + for (int i = 0; i < cnt; i++) { + AttributedCharacters ac = (AttributedCharacters)acl.get(i); + acis[i] = ac.iterator(); + } + return acis; + } + + } + +}