All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.csl.spi.GsfUtilities Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.csl.spi;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.EventObject;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.StyledDocument;
import org.netbeans.api.editor.EditorRegistry;
import org.netbeans.api.editor.document.LineDocument;
import org.netbeans.api.editor.document.LineDocumentUtils;
import org.netbeans.editor.BaseDocument;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.modules.csl.api.DataLoadersBridge;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.editor.indent.api.IndentUtils;
import org.netbeans.modules.parsing.api.Snapshot;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.spi.CursorMovedSchedulerEvent;
import org.netbeans.modules.parsing.spi.SourceModificationEvent;
import org.openide.ErrorManager;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.LineCookie;
import org.openide.cookies.OpenCookie;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.text.CloneableEditorSupport;
import org.openide.text.Line;
import org.openide.text.NbDocument;
import org.openide.util.Exceptions;
import org.openide.util.UserQuestionException;

/**
 * Misc utilities to avoid code duplication among the various language plugins
 *
 * @author Tor Norbye
 */
public final class GsfUtilities {
    private static final Logger LOG = Logger.getLogger(GsfUtilities.class.getName());

    private GsfUtilities() { // Utility class only, no instances
    }
    
    /**
     * Determines indentation level at the defined point in the document. The document
     * must provide {@link LineDocument} service otherwise 0 will be returned. On invalid 
     * offset 0 will be returned as well.
     * 
     * @param doc the document.
     * @param offset position in the document.
     * @return indentation level, in characters; 0 in case of location error.
     * @since 2.65
     */
    public static int getLineIndent(Document doc, int offset) {
        LineDocument ld = LineDocumentUtils.as(doc, LineDocument.class);
        if (ld == null) {
            return 0;
        }
        try {
            return IndentUtils.lineIndent(doc, LineDocumentUtils.getLineStart(ld, offset));
        } catch (BadLocationException | IndexOutOfBoundsException ex) {
            LOG.log(Level.WARNING, null, ex);
            return 0;
        }
    }

    /**
     * Determines indentation level at the defined point in the document.
     * This obsolete variant uses {@link BaseDocument} which promotes depedency on specialized
     * Editor UI APIs. 
     * 
     * @param doc the document.
     * @param offset position in the document.
     * @return indentation level, in characters.
     * @deprecated Use {@link #getLineIndent(javax.swing.text.Document, int) } instead.
     */
    @Deprecated
    public static int getLineIndent(BaseDocument doc, int offset) {
        return getLineIndent((Document)doc, offset);
    }

    /**
     * Adjust the indentation of the line containing the given offset to the provided
     * indentation, and return the length difference of old and new indentation.
     *
     * Copied from Indent module's "modifyIndent"
     * @deprecated Use {@link #setLineIndentation(javax.swing.text.Document, int, int).
     */
    @Deprecated
    public static int setLineIndentation(BaseDocument doc, int lineOffset, int newIndent) throws BadLocationException {
        return setLineIndentation((Document)doc, lineOffset, newIndent);
    }
    
    /**
     * Adjust the indentation of the line containing the given offset to the provided
     * indentation. Returns the length difference of old and new indentation. The document
     * must support {@link LineDocument} services, otherwise {@code -1} is returned.
     * 

* Copied from Indent module's "modifyIndent" * * @param doc the document * @param lineOffset character index into the line * @param newIndent new indentation level * @throws BadLocationException in case of position error * @return old indentation, or {@code -1} if the document is not supported. * @since 2.65 */ public static int setLineIndentation(Document doc, int lineOffset, int newIndent) throws BadLocationException { LineDocument ld = LineDocumentUtils.as(doc, LineDocument.class); if (ld == null) { return -1; } int lineStartOffset; try { lineStartOffset = LineDocumentUtils.getLineStart(ld, lineOffset); } catch (IndexOutOfBoundsException ex) { throw new BadLocationException(ex.getMessage(), lineOffset); } // Determine old indent first together with oldIndentEndOffset int indent = 0; int tabSize = -1; CharSequence docText = DocumentUtilities.getText(doc); int oldIndentEndOffset = lineStartOffset; while (oldIndentEndOffset < docText.length()) { char ch = docText.charAt(oldIndentEndOffset); if (ch == '\n') { break; } else if (ch == '\t') { if (tabSize == -1) { tabSize = IndentUtils.tabSize(doc); } // Round to next tab stop indent = (indent + tabSize) / tabSize * tabSize; } else if (Character.isWhitespace(ch)) { indent++; } else { // non-whitespace break; } oldIndentEndOffset++; } String newIndentString = IndentUtils.createIndentString(doc, newIndent); // Attempt to match the begining characters int offset = lineStartOffset; boolean different = false; int i = 0; for (; i < newIndentString.length() && lineStartOffset + i < oldIndentEndOffset; i++) { if (newIndentString.charAt(i) != docText.charAt(lineStartOffset + i)) { offset = lineStartOffset + i; newIndentString = newIndentString.substring(i); different = true; break; } } if (!different) { offset = lineStartOffset + i; newIndentString = newIndentString.substring(i); } // Replace the old indent if (offset < oldIndentEndOffset) { doc.remove(offset, oldIndentEndOffset - offset); } if (newIndentString.length() > 0) { doc.insertString(offset, newIndentString, null); } return newIndentString.length() - (oldIndentEndOffset - offset); } public static JTextComponent getOpenPane() { JTextComponent pane = EditorRegistry.lastFocusedComponent(); return pane; } public static JTextComponent getPaneFor(FileObject fo) { JTextComponent pane = getOpenPane(); if (pane != null && findFileObject(pane) == fo) { return pane; } for (JTextComponent c : EditorRegistry.componentList()) { if (findFileObject(c) == fo) { return c; } } return null; } /** * Finds or loads a document for the FileObject. If 'openIfNecessary' is false, returns {@code null} * if the document is not opened yet. * * @param fileObject file for the document * @param openIfNecessary if true, the Document will be loaded into memory, if not open at the moment. * @return document instance or {@code null} if not opened and {@code openIfNecessary} was false. * @deprecated Use {@link #getADocument(org.openide.filesystems.FileObject, boolean)}. */ @Deprecated public static BaseDocument getDocument(FileObject fileObject, boolean openIfNecessary) { return getDocument(fileObject, openIfNecessary, false); } /** * Finds or loads a document for the FileObject. If 'openIfNecessary' is false, returns {@code null} * if the document is not opened yet. * * @param fileObject file for the document * @param openIfNecessary if true, the Document will be loaded into memory, if not open at the moment. * @return document instance or {@code null} if not opened and {@code openIfNecessary} was false. * @since 2.65 */ public static Document getADocument(FileObject fileObject, boolean openIfNecessary) { return getDocument(fileObject, openIfNecessary, false); } /** * see org.openide.text.DataEditorSupport#BIG_FILE_THRESHOLD_MB */ private static final long BIG_FILE_THRESHOLD_MB = Integer.getInteger("org.openide.text.big.file.size", 5) * 1024 * 1024; /** * Load the document for the given fileObject. * @param fileObject the file whose document we want to obtain * @param openIfNecessary If true, block if necessary to open the document. If false, will only return the * document if it is already open. * @param skipLarge If true, check the file size, and if the file is really large (defined by * openide.loaders), then skip it (otherwise we could end up with a large file warning). * @return * @deprecated Use {@link #getADocument(org.openide.filesystems.FileObject, boolean, boolean)}. */ @Deprecated public static BaseDocument getDocument(FileObject fileObject, boolean openIfNecessary, boolean skipLarge) { if (skipLarge) { // Make sure we're not dealing with a huge file! // Causes issues like 132306 // openide.loaders/src/org/openide/text/DataEditorSupport.java // has an Env#inputStream method which posts a warning to the user // if the file is greater than 1Mb... //SG_ObjectIsTooBig=The file {1} seems to be too large ({2,choice,0#{2}b|1024#{3} Kb|1100000#{4} Mb|1100000000#{5} Gb}) to safely open. \n\ // Opening the file could cause OutOfMemoryError, which would make the IDE unusable. Do you really want to open it? // Apparently there is a way to handle this // (see issue http://www.netbeans.org/issues/show_bug.cgi?id=148702 ) // but for many cases, the user probably doesn't want really large files as indicated // by the skipLarge parameter). if (fileObject.getSize () > BIG_FILE_THRESHOLD_MB) { return null; } } try { EditorCookie ec = fileObject.isValid() ? DataLoadersBridge.getDefault().getCookie(fileObject, EditorCookie.class) : null; if (ec != null) { if (openIfNecessary) { try { return (BaseDocument) ec.openDocument(); } catch (UserQuestionException uqe) { uqe.confirmed(); return (BaseDocument) ec.openDocument(); } } else { return (BaseDocument) ec.getDocument(); } } } catch (IOException ex) { LOG.log(Level.WARNING, null, ex); } return null; } /** * Load the document for the given fileObject. * @param fileObject the file whose document we want to obtain * @param openIfNecessary If true, block if necessary to open the document. If false, will only return the * document if it is already open. * @param skipLarge If true, check the file size, and if the file is really large (defined by * openide.loaders), then skip it (otherwise we could end up with a large file warning). * @return * @since 2.65 */ public static Document getADocument(FileObject fileObject, boolean openIfNecessary, boolean skipLarge) { return getDocument(fileObject, openIfNecessary, skipLarge); } @Deprecated // Use getDocument instead public static BaseDocument getBaseDocument(FileObject fileObject, boolean forceOpen) { return getDocument(fileObject, forceOpen); } public static FileObject findFileObject(Document doc) { DataObject dobj = (DataObject)doc.getProperty(Document.StreamDescriptionProperty); if (dobj == null) { return null; } return dobj.getPrimaryFile(); } public static FileObject findFileObject(JTextComponent target) { Document doc = target.getDocument(); return findFileObject(doc); } // Copied from UiUtils. Shouldn't this be in a common library somewhere? public static boolean open(final FileObject fo, final int offset, final String search) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { public @Override void run() { doOpen(fo, offset, search); } }); return true; // not exactly accurate, but.... } return doOpen(fo, offset, search); } // Private methods --------------------------------------------------------- private static boolean doOpen(FileObject fo, int offset, String search) { try { DataObject od = DataObject.find(fo); EditorCookie ec = od.getCookie(EditorCookie.class); LineCookie lc = od.getCookie(LineCookie.class); // If the caller hasn't specified an offset, and the document is // already open, don't jump to a particular line! if (ec != null && offset == -1 && ec.getDocument() != null && search == null) { ec.open(); return true; } // Simple text search if no known offset (e.g. broken/unparseable source) if ((search != null) && (offset == -1)) { StyledDocument doc = NbDocument.getDocument(od); try { String text = doc.getText(0, doc.getLength()); int caretDelta = search.indexOf('^'); if (caretDelta != -1) { search = search.substring(0, caretDelta) + search.substring(caretDelta+1); } else { caretDelta = 0; } offset = text.indexOf(search); if (offset != -1) { offset += caretDelta; } } catch (BadLocationException ble) { LOG.log(Level.WARNING, null, ble); } } return NbDocument.openDocument(od, offset, Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FOCUS); } catch (IOException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } return false; } public static void extractZip(final FileObject extract, final FileObject dest) throws IOException { File extractFile = FileUtil.toFile(extract); extractZip(dest, new BufferedInputStream(new FileInputStream(extractFile))); } // Based on openide/fs' FileUtil.extractJar private static void extractZip(final FileObject fo, final InputStream is) throws IOException { FileSystem fs = fo.getFileSystem(); fs.runAtomicAction( new FileSystem.AtomicAction() { public @Override void run() throws IOException { extractZipImpl(fo, is); } } ); } /** Does the actual extraction of the Jar file. */ // Based on openide/fs' FileUtil.extractJarImpl private static void extractZipImpl(FileObject fo, InputStream is) throws IOException { ZipEntry je; ZipInputStream jis = new ZipInputStream(is); while ((je = jis.getNextEntry()) != null) { String name = je.getName(); if (name.toLowerCase().startsWith("meta-inf/")) { continue; // NOI18N } if (je.isDirectory()) { FileUtil.createFolder(fo, name); continue; } // copy the file FileObject fd = FileUtil.createData(fo, name); FileLock lock = fd.lock(); try { OutputStream os = fd.getOutputStream(lock); try { FileUtil.copy(jis, os); } finally { os.close(); } } finally { lock.releaseLock(); } } } /** Return true iff we're editing code templates */ public static boolean isCodeTemplateEditing(Document doc) { // Copied from editor/codetemplates/src/org/netbeans/lib/editor/codetemplates/CodeTemplateInsertHandler.java String EDITING_TEMPLATE_DOC_PROPERTY = "processing-code-template"; // NOI18N String CT_HANDLER_DOC_PROPERTY = "code-template-insert-handler"; // NOI18N return doc.getProperty(EDITING_TEMPLATE_DOC_PROPERTY) == Boolean.TRUE || doc.getProperty(CT_HANDLER_DOC_PROPERTY) != null; } public static boolean isRowWhite(CharSequence text, int offset) throws BadLocationException { try { // Search forwards for (int i = offset; i < text.length(); i++) { char c = text.charAt(i); if (c == '\n') { break; } if (!Character.isWhitespace(c)) { return false; } } // Search backwards for (int i = offset-1; i >= 0; i--) { char c = text.charAt(i); if (c == '\n') { break; } if (!Character.isWhitespace(c)) { return false; } } return true; } catch (Exception ex) { BadLocationException ble = new BadLocationException(offset + " out of " + text.length(), offset); ble.initCause(ex); throw ble; } } public static boolean isRowEmpty(CharSequence text, int offset) throws BadLocationException { try { if (offset < text.length()) { char c = text.charAt(offset); if (!(c == '\n' || (c == '\r' && (offset == text.length()-1 || text.charAt(offset+1) == '\n')))) { return false; } } if (!(offset == 0 || text.charAt(offset-1) == '\n')) { // There's previous stuff on this line return false; } return true; } catch (Exception ex) { BadLocationException ble = new BadLocationException(offset + " out of " + text.length(), offset); ble.initCause(ex); throw ble; } } public static int getRowLastNonWhite(CharSequence text, int offset) throws BadLocationException { try { // Find end of line int i = offset; for (; i < text.length(); i++) { char c = text.charAt(i); if (c == '\n' || (c == '\r' && (i == text.length()-1 || text.charAt(i+1) == '\n'))) { break; } } // Search backwards to find last nonspace char from offset for (i--; i >= 0; i--) { char c = text.charAt(i); if (c == '\n') { return -1; } if (!Character.isWhitespace(c)) { return i; } } return -1; } catch (Exception ex) { BadLocationException ble = new BadLocationException(offset + " out of " + text.length(), offset); ble.initCause(ex); throw ble; } } public static int getRowFirstNonWhite(CharSequence text, int offset) throws BadLocationException { try { // Find start of line int i = offset-1; if (i < text.length()) { for (; i >= 0; i--) { char c = text.charAt(i); if (c == '\n') { break; } } i++; } // Search forwards to find first nonspace char from offset for (; i < text.length(); i++) { char c = text.charAt(i); if (c == '\n') { return -1; } if (!Character.isWhitespace(c)) { return i; } } return -1; } catch (Exception ex) { BadLocationException ble = new BadLocationException(offset + " out of " + text.length(), offset); ble.initCause(ex); throw ble; } } public static int getRowStart(CharSequence text, int offset) throws BadLocationException { try { // Search backwards for (int i = offset-1; i >= 0; i--) { char c = text.charAt(i); if (c == '\n') { return i+1; } } return 0; } catch (Exception ex) { BadLocationException ble = new BadLocationException(offset + " out of " + text.length(), offset); ble.initCause(ex); throw ble; } } public static int getRowEnd(CharSequence text, int offset) throws BadLocationException { try { // Search backwards for (int i = offset; i < text.length(); i++) { char c = text.charAt(i); if (c == '\n') { return i; } } return text.length(); } catch (Exception ex) { BadLocationException ble = new BadLocationException(offset + " out of " + text.length(), offset); ble.initCause(ex); throw ble; } } public static boolean endsWith(StringBuilder sb, String s) { int len = s.length(); if (sb.length() < len) { return false; } for (int i = sb.length()-len, j = 0; j < len; i++, j++) { if (sb.charAt(i) != s.charAt(j)) { return false; } } return true; } public static String truncate(String s, int length) { assert length > 3; // Not for short strings if (s.length() <= length) { return s; } else { return s.substring(0, length-3) + "..."; } } /** * Gets the last known offset of the editor caret. * * @param snapshot The snapshot to get the offset for. * @param event The event that can contain offset information. Can be null. * * @return The last know caret offset or -1. */ public static int getLastKnownCaretOffset(Snapshot snapshot, EventObject event) { // Try scheduler event first if (event instanceof CursorMovedSchedulerEvent) { return ((CursorMovedSchedulerEvent) event).getCaretOffset(); } // defect #221922: getDocument() false forces DObj construction, which I'd like to avoid // during parsing. Since != -1 is only returned iff opened Editor component is found, // we can as well search in the opposite direction, starting from opened editors. FileObject snapshotFile = snapshot.getSource().getFileObject(); Document snapshotDoc = null; if (snapshotFile != null) { for(JTextComponent jtc : EditorRegistry.componentList()) { if (snapshotFile == NbEditorUtilities.getFileObject(jtc.getDocument())) { // double check: check the document is the same: snapshotDoc = snapshot.getSource().getDocument(false); if (snapshotDoc == null || snapshotDoc == jtc.getDocument()) { Caret c = jtc.getCaret(); if (c != null) { return c.getDot(); } } break; } } } // if the file was NOT null, the document is not opened in any editor (result of the previous search) // so in that case we leave snapshotDoc null and avoid creation of DObj. if (snapshotDoc == null && snapshotFile == null) { snapshotDoc = snapshot.getSource().getDocument(false); } if (snapshotDoc != null) { for(JTextComponent jtc : EditorRegistry.componentList()) { if (snapshotDoc == jtc.getDocument()) { Caret c = jtc.getCaret(); if (c != null) { return c.getDot(); } } } } // Finally, try the enforced caret offset (eg. enforced by tests) Integer enforcedCaretOffset = enforcedCaretOffsets.get(snapshot.getSource()); if (enforcedCaretOffset != null) { return enforcedCaretOffset; } if (event instanceof SourceModificationEvent) { return ((SourceModificationEvent)event).getAffectedEndOffset(); } return -1; } /** * Gets the CloneableEditorSupport for given FileObject * * @param fo A FileObject to get the CES for. * @return Instance of CloneableEditorSupport */ public static CloneableEditorSupport findCloneableEditorSupport(FileObject fo) { try { DataObject dob = DataObject.find(fo); Object obj = dob.getCookie(OpenCookie.class); if (obj instanceof CloneableEditorSupport) { return (CloneableEditorSupport)obj; } obj = dob.getCookie(org.openide.cookies.EditorCookie.class); if (obj instanceof CloneableEditorSupport) { return (CloneableEditorSupport)obj; } } catch (DataObjectNotFoundException ex) { Exceptions.printStackTrace(ex); } return null; } // this is called from tests /* package */ static void setLastKnowCaretOffset(Source source, int offset) { enforcedCaretOffsets.put(source, offset); } private static final Map enforcedCaretOffsets = new WeakHashMap(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy