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

org.netbeans.modules.versioning.util.Utils Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show 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.versioning.util;

import java.awt.Cursor;
import java.awt.EventQueue;
import javax.swing.tree.TreePath;
import org.openide.util.RequestProcessor;
import org.openide.util.NbBundle;
import org.openide.util.actions.Presenter;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.loaders.DataShadow;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.awt.Actions;
import org.openide.nodes.Node;
import org.openide.text.CloneableEditorSupport;
import org.openide.windows.CloneableOpenSupport;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.modules.versioning.spi.VCSContext;
import org.netbeans.modules.versioning.spi.VersioningSupport;

import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
import javax.swing.text.EditorKit;
import javax.swing.text.BadLocationException;
import javax.swing.*;
import java.io.*;
import java.util.prefs.Preferences;
import java.util.prefs.BackingStoreException;
import java.util.*;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.awt.Rectangle;
import java.awt.Point;
import java.text.MessageFormat;
import java.beans.PropertyChangeListener;
import java.beans.VetoableChangeListener;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Set;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.api.queries.FileEncodingQuery;
import org.netbeans.api.queries.VersioningQuery;
import org.netbeans.modules.versioning.core.api.VCSFileProxy;
import org.netbeans.modules.versioning.spi.VersioningSystem;
import org.openide.ErrorManager;
import org.openide.awt.AcceleratorBinding;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.OpenCookie;
import org.openide.filesystems.FileLock;
import org.openide.util.Lookup;
import org.openide.util.Mutex;
import org.openide.util.Utilities;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;

/**
 * Utilities class.
 *
 * @author Maros Sandor
 */
public final class Utils {

    private static final Logger LOG = Logger.getLogger(Utils.class.getName());
    
    /**
     * Request processor for quick tasks.
     */
    private static final RequestProcessor vcsRequestProcessor = new RequestProcessor("Versioning", 1);

    /**
     * Request processor for long running tasks.
     */
    private static final RequestProcessor vcsBlockingRequestProcessor = new RequestProcessor("Versioning long tasks", 1);

    /**
     * Request processor for parallel tasks.
     */
    private static final RequestProcessor vcsParallelRequestProcessor = new RequestProcessor("Versioning parallel tasks", 5, true);

    /**
     * Metrics logger
     */
    private static final Logger METRICS_LOG = Logger.getLogger("org.netbeans.ui.metrics.vcs");

    /**
     * Metrics logger
     */
    private static final Logger UIGESTURES_LOG = Logger.getLogger("org.netbeans.ui.vcs"); //NOI18N

    /**
     * Keeps track about already logged metrics events
     */
    private static final Set metrics = new HashSet(3);

    private static File tempDir;
    
    /**
     * Keeps forbidden folders without metadata
     */
    private static final Set forbiddenFolders;
    static {
        Set files = new HashSet();
        try {
            String forbidden = System.getProperty("versioning.forbiddenFolders", ""); //NOI18N
            files.addAll(Arrays.asList(forbidden.split("\\;"))); //NOI18N
            files.remove(""); //NOI18N
        } catch (Exception e) {
            Logger.getLogger(Utils.class.getName()).log(Level.INFO, e.getMessage(), e);
        }
        forbiddenFolders = files;
    }

    private Utils() {
    }

    /**
     * Creates a task that will run in the Versioning RequestProcessor (with has throughput of 1). The runnable may take long
     * to execute (connet through network, etc).
     *
     * @param runnable Runnable to run
     * @return RequestProcessor.Task created task
     */
    public static RequestProcessor.Task createTask(Runnable runnable) {
        return vcsBlockingRequestProcessor.create(runnable);
    }

    /**
     * Runs the runnable in the Versioning RequestProcessor (with has throughput of 1). The runnable must not take long
     * to execute (connet through network, etc).
     *
     * @param runnable Runnable to run
     */
    public static void post(Runnable runnable) {
        post(runnable, 0);
    }

    /**
     * Runs the runnable in the Versioning RequestProcessor (which has throughput of 1). The runnable must not take long
     * to execute (connect through network, etc).
     *
     * @param runnable Runnable to run
     * @param timeToWait delay before starting the task
     */
    public static void post (Runnable runnable, int timeToWait) {
        vcsRequestProcessor.post(runnable, timeToWait);
    }

    /**
     * Runs the runnable in the Versioning RequestProcessor (which has throughput of 5).
     *
     * @param runnable Runnable to run
     * @param timeToWait delay before starting the task
     */
    public static void postParallel (Runnable runnable, int timeToWait) {
        vcsParallelRequestProcessor.post(runnable, timeToWait);
    }

    /**
     * Tests for ancestor/child file relationsip.
     *
     * @param ancestor supposed ancestor of the file
     * @param file a file
     * @return true if ancestor is an ancestor folder of file OR both parameters are equal, false otherwise
     */
    public static boolean isAncestorOrEqual(File ancestor, File file) {
        if (VersioningSupport.isFlat(ancestor)) {
            return ancestor.equals(file) || ancestor.equals(file.getParentFile()) && !file.isDirectory();
        }
        
        String filePath = file.getAbsolutePath();
        String ancestorPath = ancestor.getAbsolutePath();
        if(Utilities.isWindows()) {
            if(filePath.indexOf("~") < 0 && ancestorPath.indexOf("~") < 0) {
                if(filePath.length() < ancestorPath.length()) {
                    return false;
                }
            }
        } else if (Utilities.isMac()) {
            // Mac is not case sensitive, cannot use the else statement
            if(filePath.length() < ancestorPath.length()) {
                return false;
            }
        } else {
            if(!filePath.startsWith(ancestorPath)) {
                return false;
            }
        }

        // get sure as it still could be something like:
        // ancestor: /home/dil
        // file:     /home/dil1/dil2
        for (; file != null; file = file.getParentFile()) {
            if (file.equals(ancestor)) return true;
        }
        return false;
    }

    /**
     * Tests whether all files belong to the same data object.
     *
     * @param files array of Files
     * @return true if all files share common DataObject (even null), false otherwise
     */
    public static boolean shareCommonDataObject(File[] files) {
        if (files == null || files.length < 2) return true;
        DataObject common = findDataObject(files[0]);
        for (int i = 1; i < files.length; i++) {
            DataObject dao = findDataObject(files[i]);
            if (dao != common && (dao == null || !dao.equals(common))) return false;
        }
        return true;
    }

    /**
     * @param file
     * @return Set all files that belong to the same DataObject as the argument
     */
    public static Set getAllDataObjectFiles(File file) {
        Set filesToCheckout = new HashSet(2);
        filesToCheckout.add(file);
        FileObject fo = FileUtil.toFileObject(file);
        if (fo != null) {
            try {
                DataObject dao = DataObject.find(fo);
                Set fileObjects = dao.files();
                for (FileObject fileObject : fileObjects) {
                    filesToCheckout.add(FileUtil.toFile(fileObject));
                }
            } catch (DataObjectNotFoundException e) {
                // no dataobject, never mind
            }
        }
        return filesToCheckout;
    }

    /**
     * Some folders are special and versioning should not look for metadata in
     * them. Folders like /net with automount enabled may take a long time to
     * answer I/O on their children, so
     * VCSFileProxy.exists("/net/.git") will freeze until it
     * timeouts. You should call this method before asking any I/O on children
     * of this folder you are unsure to actually exist. This does not mean
     * however that whole subtree should be excluded from version control, only
     * that you should not look for the metadata directly in this folder.
     * Returns true if the given folder is among such folders.
     *
     * @param folderPath path to a folder to query
     * @return true if the folder identified by the given path
     * should be skipped when searching for metadata.
     * @since 1.54
     * @deprecated  use isForbiddenFolder(File) or isForbiddenFolder(VCSFileProxy)
     */
    @Deprecated
    public static boolean isForbiddenFolder (String folderPath) {
        return forbiddenFolders.contains(folderPath);
    }

    /**
     * Some folders are special and versioning should not look for metadata in
     * them. Folders like /net with automount enabled may take a long time to
     * answer I/O on their children, so
     * VCSFileProxy.exists("/net/.git") will freeze until it
     * timeouts. You should call this method before asking any I/O on children
     * of this folder you are unsure to actually exist. This does not mean
     * however that whole subtree should be excluded from version control, only
     * that you should not look for the metadata directly in this folder.
     * Returns true if the given folder is among such folders.
     *
     * @param folderPath path to a folder to query
     * @return true if the folder identified by the given path
     * should be skipped when searching for metadata.
     * @since 1.71.0
     */
    public static boolean isForbiddenFolder (File folder) {
        return org.netbeans.modules.versioning.core.util.Utils.isForbiddenFolder(VCSFileProxy.createFileProxy(folder));
    }

    /**
     * Some folders are special and versioning should not look for metadata in
     * them. Folders like /net with automount enabled may take a long time to
     * answer I/O on their children, so
     * VCSFileProxy.exists("/net/.git") will freeze until it
     * timeouts. You should call this method before asking any I/O on children
     * of this folder you are unsure to actually exist. This does not mean
     * however that whole subtree should be excluded from version control, only
     * that you should not look for the metadata directly in this folder.
     * Returns true if the given folder is among such folders.
     *
     * @param folderPath path to a folder to query
     * @return true if the folder identified by the given path
     * should be skipped when searching for metadata.
     * @since 1.71.0
     */
    public static boolean isForbiddenFolder (VCSFileProxy folder) {
        return org.netbeans.modules.versioning.core.util.Utils.isForbiddenFolder(folder);
    }

    private static DataObject findDataObject(File file) {
        FileObject fo = FileUtil.toFileObject(file);
        if (fo != null) {
            try {
                return DataObject.find(fo);
            } catch (DataObjectNotFoundException e) {
                // ignore
            }
        }
        return null;
    }

    /**
     * Checks if the file is to be considered as textuall.
     *
     * @param file file to check
     * @return true if the file can be edited in NetBeans text editor, false otherwise
     */
    public static boolean isFileContentText(File file) {
        FileObject fo = FileUtil.toFileObject(file);
        if (fo == null) return false;
        if (fo.getMIMEType().startsWith("text")) { // NOI18N
            return true;
        }
        try {
            DataObject dao = DataObject.find(fo);
            return dao.getLookup().lookupItem(new Lookup.Template(EditorCookie.class)) != null;
        } catch (DataObjectNotFoundException e) {
            // not found, continue
        }
        return false;
    }

    /**
     * Copies all content from the supplied reader to the supplies writer and closes both streams when finished.
     *
     * @param writer where to write
     * @param reader what to read
     * @throws IOException if any I/O operation fails
     */
    public static void copyStreamsCloseAll(OutputStream writer, InputStream reader) throws IOException {
        byte [] buffer = new byte[4096];
        int n;
        while ((n = reader.read(buffer)) != -1) {
            writer.write(buffer, 0, n);
        }
        writer.close();
        reader.close();
    }

    /**
     * Copies all content from the supplied reader to the supplies writer and closes both streams when finished.
     *
     * @param writer where to write
     * @param reader what to read
     * @throws IOException if any I/O operation fails
     */
    public static void copyStreamsCloseAll(Writer writer, Reader reader) throws IOException {
        char [] buffer = new char[4096];
        int n;
        while ((n = reader.read(buffer)) != -1) {
            writer.write(buffer, 0, n);
        }
        writer.close();
        reader.close();
    }

    /**
     * Helper method to get an array of Strings from preferences.
     *
     * @param prefs storage
     * @param key key of the String array
     * @return List stored List of String or an empty List if the key was not found (order is preserved)
     */
    public static List getStringList(Preferences prefs, String key) {
        List retval = new ArrayList();
        try {
            String[] keys = prefs.keys();
            for (int i = 0; i < keys.length; i++) {
                String k = keys[i];
                if (k != null && k.startsWith(key)) {
                    int idx = Integer.parseInt(k.substring(k.lastIndexOf('.') + 1));
                    retval.add(idx + "." + prefs.get(k, null));
                }
            }
            List rv = new ArrayList(retval.size());
            rv.addAll(retval);
            for (String s : retval) {
                int pos = s.indexOf('.');
                int index = Integer.parseInt(s.substring(0, pos));
                rv.set(index, s.substring(pos + 1));
            }
            return rv;
        } catch (Exception ex) {
            Logger.getLogger(Utils.class.getName()).log(Level.INFO, null, ex);
            return new ArrayList(0);
        }
    }

    /**
     * Stores a List of Strings into Preferences node under the given key.
     *
     * @param prefs storage
     * @param key key of the String array
     * @param value List of Strings to write (order will be preserved)
     */
    public static void put(Preferences prefs, String key, List value) {
        try {
            String[] keys = prefs.keys();
            for (int i = 0; i < keys.length; i++) {
                String k = keys[i];
                if (k != null && k.startsWith(key + ".")) {
                    prefs.remove(k);
                }
            }
            int idx = 0;
            for (String s : value) {
                prefs.put(key + "." + idx++, s);
            }
        } catch (BackingStoreException ex) {
            Logger.getLogger(Utils.class.getName()).log(Level.INFO, null, ex);
        }
    }

    /**
     * Convenience method for storing array of Strings with a maximum length with LRU policy. Supplied value is
     * stored at index 0 and all items beyond (maxLength - 1) index are discarded. 
* If the value is already stored then it will be first removed from its old position. * * @param prefs storage * @param key key for the array * @param value String to store * @param maxLength maximum length of the stored array. won't be considered if < 0 */ public static void insert(Preferences prefs, String key, String value, int maxLength) { List newValues = getStringList(prefs, key); newValues.removeAll(Collections.singleton(value)); newValues.add(0, value); if (maxLength > -1 && newValues.size() > maxLength) { newValues.subList(maxLength, newValues.size()).clear(); } put(prefs, key, newValues); } /** * Convenience method to remove a array of values from a in preferences stored array of Strings * * @param prefs storage * @param key key for the array * @param values Strings to remove */ public static void removeFromArray(Preferences prefs, String key, List values) { List newValues = getStringList(prefs, key); newValues.removeAll(values); put(prefs, key, newValues); } /** * Convenience method to remove a value from a in preferences stored array of Strings * * @param prefs storage * @param key key for the array * @param value String to remove */ public static void removeFromArray(Preferences prefs, String key, String value) { List newValues = getStringList(prefs, key); newValues.removeAll(Collections.singleton(value)); put(prefs, key, newValues); } /** * Splits files/folders into 2 groups: flat folders and other files * * @param files array of files to split * @return File[][] the first array File[0] contains flat folders (@see #flatten for their direct descendants), * File[1] contains all other files */ public static File[][] splitFlatOthers(File [] files) { Set flat = new HashSet(1); for (int i = 0; i < files.length; i++) { if (VersioningSupport.isFlat(files[i])) { flat.add(files[i]); } } if (flat.isEmpty()) { return new File[][] { new File[0], files }; } else { Set allFiles = new HashSet(Arrays.asList(files)); allFiles.removeAll(flat); return new File[][] { flat.toArray(new File[flat.size()]), allFiles.toArray(new File[allFiles.size()]) }; } } /** * Flattens the given collection of files and removes those that do not respect the flat folder logic, * i.e. those that lie deeper under a flat folder. * @param roots selected files with flat folders */ public static Set flattenFiles (File[] roots, Collection files) { File[][] split = Utils.splitFlatOthers(roots); Set filteredFiles = new HashSet(files); if (split[0].length > 0) { outer: for (Iterator it = filteredFiles.iterator(); it.hasNext(); ) { File f = it.next(); // file is directly under a flat folder for (File flat : split[0]) { if (f.getParentFile().equals(flat)) { continue outer; } } // file lies under a recursive folder for (File folder : split[1]) { if (Utils.isAncestorOrEqual(folder, f)) { continue outer; } } it.remove(); } } return filteredFiles; } /** * Recursively deletes the file or directory. * * @param file file/directory to delete */ public static void deleteRecursively(File file) { File [] files = file.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { deleteRecursively(files[i]); } } file.delete(); } /** * Searches for common filesystem parent folder for given files. * * @param a first file * @param b second file * @return File common parent for both input files with the longest filesystem path or null of these files * have not a common parent */ public static File getCommonParent(File a, File b) { for (;;) { if (a.equals(b)) { return a; } else if (a.getAbsolutePath().length() > b.getAbsolutePath().length()) { a = a.getParentFile(); if (a == null) return null; } else { b = b.getParentFile(); if (b == null) return null; } } } public static String getStackTrace() { Exception e = new Exception(); e.fillInStackTrace(); StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); return sw.toString(); } /** * Copied from org.netbeans.api.xml.parsers.DocumentInputSource to save whole module dependency. * * @param doc a Document to read * @return Reader a reader that reads document's text */ public static Reader getDocumentReader(final Document doc) { final String[] str = new String[1]; Runnable run = new Runnable() { @Override public void run () { try { str[0] = doc.getText(0, doc.getLength()); } catch (javax.swing.text.BadLocationException e) { // impossible LOG.log(Level.INFO, null, e); } } }; doc.render(run); return new StringReader(str[0]); } /** * For popups invoked by keyboard determines best location for it. * * @param table source of popup event * @return Point best location for menu popup */ public static Point getPositionForPopup(JTable table) { int idx = table.getSelectedRow(); if (idx == -1) idx = 0; Rectangle rect = table.getCellRect(idx, 1, true); return rect.getLocation(); } /** * For popups invoked by keyboard determines best location for it. * * @param list source of popup event * @return Point best location for menu popup */ public static Point getPositionForPopup(JList list) { int idx = list.getSelectedIndex(); if (idx == -1) idx = 0; Rectangle rect = list.getCellBounds(idx, idx); rect.x += 10; rect.y += rect.height; return rect.getLocation(); } /** * For popups invoked by keyboard determines best location for it. * * @param tree source of popup event * @return Point best location for menu popup */ public static Point getPositionForPopup(JTree tree) { TreePath path = tree.getSelectionPath(); if (path == null) path = tree.getPathForRow(0); Rectangle rect = tree.getPathBounds(path); rect.x += 10; rect.y += rect.height; return rect.getLocation(); } /** * Creates a menu item from an action. * * @param action an action * @return JMenuItem */ public static JMenuItem toMenuItem(Action action) { JMenuItem item; if (action instanceof Presenter.Menu) { item = ((Presenter.Menu) action).getMenuPresenter(); } else { item = new JMenuItem(); Actions.connect(item, action, false); } return item; } /** * Creates a temporary folder. The folder has deleteOnExit flag set. * @return */ public static File getTempFolder() { return getTempFolder(true); } /** * Creates a temporary folder. The folder will have deleteOnExit flag set to deleteOnExit. * @return */ public static File getTempFolder(boolean deleteOnExit) { File tmpDir = getTempDir(deleteOnExit); for (;;) { File dir = new File(tmpDir, "vcs-" + Long.toString(System.currentTimeMillis())); // NOI18N if (!dir.exists() && dir.mkdirs()) { if (deleteOnExit) { dir.deleteOnExit(); } return FileUtil.normalizeFile(dir); } } } /** * Utility method to word-wrap a String. * * @param s String to wrap * @param maxLineLength maximum length of one line. If less than 1 no wrapping will occurr * @return String wrapped string */ public static String wordWrap(String s, int maxLineLength) { int n = s.length() - 1; if (maxLineLength < 1 || n < maxLineLength) return s; StringBuilder sb = new StringBuilder(); int currentWrap = 0; for (;;) { int nextWrap = currentWrap + maxLineLength - 1; if (nextWrap >= n) { sb.append(s.substring(currentWrap)); break; } int idx = s.lastIndexOf(' ', nextWrap + 1); if (idx > currentWrap) { sb.append(s.substring(currentWrap, idx).trim()); currentWrap = idx + 1; } else { sb.append(s.substring(currentWrap, nextWrap + 1)); currentWrap = nextWrap + 1; } sb.append('\n'); } return sb.toString(); } /** * Computes display name of an action based on its context. * * @param clazz caller class for bundle location * @param baseName base bundle name * @param ctx action's context * @return String full name of the action, eg. Show "File.java" Annotations */ public static String getActionName(Class clazz, String baseName, VCSContext ctx) { Set nodes = ctx.getRootFiles(); int objectCount = nodes.size(); // if all nodes represent project node the use plain name // It avoids "Show changes 2 files" on project node // caused by fact that project contains two source groups. Node[] activatedNodes = ctx.getElements().lookupAll(Node.class).toArray(new Node[0]); boolean projectsOnly = true; for (int i = 0; i < activatedNodes.length; i++) { Node activatedNode = activatedNodes[i]; Project project = (Project) activatedNode.getLookup().lookup(Project.class); if (project == null) { projectsOnly = false; break; } } if (projectsOnly) objectCount = activatedNodes.length; if (objectCount == 0) { return NbBundle.getBundle(clazz).getString(baseName); } else if (objectCount == 1) { if (projectsOnly) { String dispName = ProjectUtils.getInformation((Project) activatedNodes[0].getLookup().lookup(Project.class)).getDisplayName(); return NbBundle.getMessage(clazz, baseName + "_Context", // NOI18N dispName); } String name; FileObject fo = (FileObject) activatedNodes[0].getLookup().lookup(FileObject.class); if (fo != null) { name = fo.getNameExt(); } else { DataObject dao = (DataObject) activatedNodes[0].getLookup().lookup(DataObject.class); if (dao instanceof DataShadow) { dao = ((DataShadow) dao).getOriginal(); } if (dao != null) { name = dao.getPrimaryFile().getNameExt(); } else { name = activatedNodes[0].getDisplayName(); } } return MessageFormat.format(NbBundle.getBundle(clazz).getString(baseName + "_Context"), name); // NOI18N } else { if (projectsOnly) { try { return MessageFormat.format(NbBundle.getBundle(clazz).getString(baseName + "_Projects"), objectCount); // NOI18N } catch (MissingResourceException ex) { // ignore use files alternative bellow } } return MessageFormat.format(NbBundle.getBundle(clazz).getString(baseName + "_Context_Multiple"), objectCount); // NOI18N } } /** * Computes display name of a context. * * @param ctx a context * @return String short display name of the context, eg. File.java, 3 Files, 2 Projects, etc. */ public static String getContextDisplayName(VCSContext ctx) { // TODO: reuse this code in getActionName() Set nodes = ctx.getFiles(); int objectCount = nodes.size(); // if all nodes represent project node the use plain name // It avoids "Show changes 2 files" on project node // caused by fact that project contains two source groups. Node[] activatedNodes = ctx.getElements().lookupAll(Node.class).toArray(new Node[0]); boolean projectsOnly = true; for (int i = 0; i < activatedNodes.length; i++) { Node activatedNode = activatedNodes[i]; Project project = (Project) activatedNode.getLookup().lookup(Project.class); if (project == null) { projectsOnly = false; break; } } if (projectsOnly) objectCount = activatedNodes.length; if (objectCount == 0) { return null; } else if (objectCount == 1) { if (projectsOnly) { return ProjectUtils.getInformation((Project) activatedNodes[0].getLookup().lookup(Project.class)).getDisplayName(); } FileObject fo = (FileObject) activatedNodes[0].getLookup().lookup(FileObject.class); if (fo != null) { return fo.getNameExt(); } else { DataObject dao = (DataObject) activatedNodes[0].getLookup().lookup(DataObject.class); if (dao instanceof DataShadow) { dao = ((DataShadow) dao).getOriginal(); } if (dao != null) { return dao.getPrimaryFile().getNameExt(); } else { return activatedNodes[0].getDisplayName(); } } } else { if (projectsOnly) { try { return MessageFormat.format(NbBundle.getBundle(Utils.class).getString("MSG_ActionContext_MultipleProjects"), objectCount); // NOI18N } catch (MissingResourceException ex) { // ignore use files alternative bellow } } return MessageFormat.format(NbBundle.getBundle(Utils.class).getString("MSG_ActionContext_MultipleFiles"), objectCount); // NOI18N } } /** * Open a read-only view of the file in editor area. * * @param fo a file to open * @param revision revision of the file * @return editor support opening the file */ public static CloneableEditorSupport openFile(FileObject fo, String revision) { ViewEnv env = new ViewEnv(fo); CloneableEditorSupport ces = new ViewCES(env, fo.getNameExt() + " @ " + revision, FileEncodingQuery.getEncoding(fo)); // NOI18N ces.view(); return ces; } /** * Asks for permission to scan a given folder for versioning metadata. Misconfigured automount daemons may * try to look for a "CVS" server if asked for "/net/CVS/Entries" file for example causing hangs and full load. * Versioning systems must NOT scan a folder if this method returns true and should consider it as unversioned. * * @deprecated Use {@link VersioningSupport#isExcluded(java.io.File) } instead * @param folder a folder to query * @link http://www.netbeans.org/issues/show_bug.cgi?id=105161 * @return true if scanning for versioning system metadata is forbidden in the given folder, false otherwise */ @Deprecated public static boolean isScanForbidden(File folder) { return VersioningSupport.isExcluded(folder); } /** * Opens a file in the editor area. * * @param file a File to open */ public static void openFile(File file) { FileObject fo = FileUtil.toFileObject(file); if (fo != null) { try { DataObject dao = DataObject.find(fo); final OpenCookie oc = dao.getLookup().lookup(OpenCookie.class); if (oc != null) { Mutex.EVENT.readAccess(new Runnable() { @Override public void run () { oc.open(); } }); } } catch (DataObjectNotFoundException e) { // nonexistent DO, do nothing } } } private static final Object ENCODING_LOCK = new Object(); private static Map fileToCharset; private static Map fileToFileObject; /** * Retrieves the Charset for the referenceFile and associates it weakly with * the given file. A following getAssociatedEncoding() call for * the file will then return the referenceFile-s Charset. * * @param referenceFile the file which charset has to be used when encoding file * @param file file to be encoded with the referenceFile-s charset * */ public static void associateEncoding(File referenceFile, File file) { FileObject refFO = FileUtil.toFileObject(referenceFile); if (refFO == null || refFO.isFolder()) { return; } FileObject fo = FileUtil.toFileObject(file); if (fo == null || fo.isFolder()) { return; } Charset c = FileEncodingQuery.getEncoding(refFO); if (c != null) { synchronized(ENCODING_LOCK) { if (fileToFileObject == null) { fileToFileObject = new WeakHashMap(); } fileToFileObject.put(file, fo); } associateEncoding(fo, c); } } /** * Retrieves the Charset for the referenceFile and associates it weakly with * the given file. A following getAssociatedEncoding() call for * the file will then return the referenceFile-s Charset. * * @param referenceFile the file which charset has to be used when encoding file * @param file file to be encoded with the referenceFile-s charset * */ public static void associateEncoding(FileObject refFo, FileObject fo) { if(refFo == null || refFo.isFolder()) { return; } if(fo == null || fo.isFolder()) { return; } Charset c = FileEncodingQuery.getEncoding(refFo); associateEncoding(fo, c); } /** * Associates a given charset weakly with * the given file. A following getAssociatedEncoding() call for * the file will then return the referenceFile-s Charset. * * @param file file to be encoded with the referenceFile-s charset * */ public static void associateEncoding (File file, Charset charset) { FileObject fo = FileUtil.toFileObject(file); if(fo == null) { LOG.log(Level.WARNING, "associateEncoding() no file object available for {0}", file); // NOI18N return; } associateEncoding(fo, charset); } /** * Associates a given charset weakly with * the given file. A following getAssociatedEncoding() call for * the file will then return the referenceFile-s Charset. * * @param file file to be encoded with the referenceFile-s charset * */ public static void associateEncoding (FileObject file, Charset charset) { if(charset == null) { return; } synchronized(ENCODING_LOCK) { if(fileToCharset == null) { fileToCharset = new WeakHashMap(); } fileToCharset.put(file, charset); } } /** * Returns a charset for the given file if it was previously registered via associateEncoding() * * @param fo file for which the encoding has to be retrieved * @return the charset the given file has to be encoded with */ public static Charset getAssociatedEncoding(FileObject fo) { try { synchronized(ENCODING_LOCK) { if(fileToCharset == null || fileToCharset.isEmpty() || fo == null || fo.isFolder()) { return null; } Charset c = fileToCharset.get(fo); return c; } } catch (Throwable t) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, t); return null; } } public static Reader createReader(File file) throws FileNotFoundException { FileObject fo = FileUtil.toFileObject(file); if (fo == null) { return new FileReader(file); } else { return createReader(fo); } } public static Reader createReader(FileObject file) throws FileNotFoundException { return new InputStreamReader(file.getInputStream(), FileEncodingQuery.getEncoding(file)); } /** * Convenience method for awkward Logger invocation. * * @param caller caller object for logger name determination * @param e exception that defines the error */ public static void logInfo(Class caller, Throwable e) { Logger.getLogger(caller.getName()).log(Level.INFO, e.getMessage(), e); } /** * Convenience method for awkward Logger invocation. * * @param caller caller object for logger name determination * @param e exception that defines the error */ public static void logWarn(Class caller, Throwable e) { Logger.getLogger(caller.getName()).log(Level.WARNING, e.getMessage(), e); } /** * Convenience method for awkward Logger invocation. * * @param caller caller object for logger name determination * @param e exception that defines the error */ public static void logError(Object caller, Throwable e) { Logger.getLogger(caller.getClass().getName()).log(Level.SEVERE, e.getMessage(), e); } /** * Convenience method for awkward Logger invocation. * * @param caller caller object for logger name determination * @param e exception that defines the error */ public static void logFine(Object caller, Exception e) { Logger.getLogger(caller.getClass().getName()).log(Level.FINE, e.getMessage(), e); } /** * Convenience method for awkward Logger invocation. * * @param caller caller object for logger name determination * @param e exception that defines the error */ public static void logWarn(Object caller, Throwable e) { logWarn(caller.getClass(), e); } /** * Logs a vcs client usage. * * @param vcs - the particular vcs "SVN", "CVS", "CC", "HG", ... * @param client - the particular vcs cient "CLI", "JAVAHL", "JAVALIB" */ public static void logVCSClientEvent(String vcs, String client) { String key = "USG_VCS_CLIENT" + vcs; if (checkMetricsKey(key)) return; LogRecord rec = new LogRecord(Level.INFO, "USG_VCS_CLIENT"); rec.setParameters(new Object[] { vcs, client }); rec.setLoggerName(METRICS_LOG.getName()); METRICS_LOG.log(rec); } /** * Logs a vcs external repository name. * * @param vcs - the particular vcs "SVN", "CVS", "CC", "HG", "GIT", ... * @param repositoryUrl - external repository url to log or null if the repository is local */ public static void logVCSExternalRepository (String vcs, String repositoryUrl) { String repositoryIdent = getKnownRepositoryFor(repositoryUrl); String key = "USG_VCS_REPOSITORY" + vcs + repositoryIdent; //NOI18N if (checkMetricsKey(key)) return; LogRecord rec = new LogRecord(Level.INFO, "USG_VCS_REPOSITORY"); //NOI18N rec.setParameters(new Object[] { vcs, repositoryIdent }); rec.setLoggerName(METRICS_LOG.getName()); METRICS_LOG.log(rec); } /** * Logs a vcs client action usage. * * @param vcs - the particular vcs "SVN", "CVS", "CC", "HG", ... */ public static void logVCSActionEvent(String vcs) { String key = "USG_VCS_ACTION" + vcs; if (checkMetricsKey(key)) return; LogRecord rec = new LogRecord(Level.INFO, "USG_VCS_ACTION"); rec.setParameters(new Object[] { vcs }); rec.setLoggerName(METRICS_LOG.getName()); METRICS_LOG.log(rec); } /** * Logs vcs command usage. * * @param vcs - the particular vcs "GIT", "HG", ... * @param time - time in millis the command took to finish * @param modifications - number of modified/created/deleted files during * the command's progress * @param command - command name * @param external - true if the command was invoked externally * (e.g. on commandline) and not from within the IDE. */ public static void logVCSCommandUsageEvent (String vcs, long time, long modifications, String command, boolean external) { if (command == null) { command = "UNKNOWN"; //NOI18N } LogRecord rec = new LogRecord(Level.INFO, "USG_VCS_CMD"); //NOI18N String cmdType = external ? "EXTERNAL" : "INTERNAL"; rec.setResourceBundle(NbBundle.getBundle(Utils.class)); rec.setResourceBundleName(Utils.class.getPackage().getName() + ".Bundle"); //NOI18N rec.setParameters(new Object[] { vcs, time, modifications, command, cmdType }); rec.setLoggerName(UIGESTURES_LOG.getName()); UIGESTURES_LOG.log(rec); } private static boolean checkMetricsKey(String key) { synchronized (metrics) { if (metrics.contains(key)) { return true; } else { metrics.add(key); } } return false; } private static String getKnownRepositoryFor (String repositoryUrl) { if (repositoryUrl == null) { return "LOCAL"; //NOI18N } repositoryUrl = repositoryUrl.toLowerCase(); if (repositoryUrl.contains("github.com")) { //NOI18N return "GITHUB"; //NOI18N } else if (repositoryUrl.contains("gitorious.org")) { //NOI18N return "GITORIOUS"; //NOI18N } else if (repositoryUrl.contains("bitbucket.org")) { //NOI18N return "BITBUCKET"; //NOI18N } else if (repositoryUrl.contains("sourceforge.net")) { //NOI18N return "SOURCEFORGE"; //NOI18N } else if (repositoryUrl.contains("googlecode.com") //NOI18N || repositoryUrl.contains("code.google.com") //NOI18N || repositoryUrl.contains("googlesource.com")) { //NOI18N return "GOOGLECODE"; //NOI18N } else if (repositoryUrl.contains("kenai.com")) { //NOI18N return "KENAI"; //NOI18N } else if (repositoryUrl.contains("java.net")) { //NOI18N return "JAVANET"; //NOI18N } else if (repositoryUrl.contains("netbeans.org")) { //NOI18N return "NETBEANS"; //NOI18N } else if (repositoryUrl.contains("codeplex.com")) { //NOI18N return "CODEPLEX"; //NOI18N } else if (repositoryUrl.contains(".eclipse.org")) { //NOI18N return "ECLIPSE"; //NOI18N } else { return "OTHER"; //NOI18N } } /** * Sets or resets r/o flag. * * @param file a file to modify * @param ro true to make the file r/o, false to make the file r/w */ public static void setReadOnly(File file, boolean readOnly) { // TODO: update for Java6 String [] args; if (Utilities.isWindows()) { args = new String [] {"attrib", readOnly ? "+r": "-r", file.getName()}; //NOI18N } else { args = new String [] {"chmod", readOnly ? "u-w": "u+w", file.getName()}; //NOI18N } try { Process process = Runtime.getRuntime().exec(args, null, file.getParentFile()); process.waitFor(); } catch (Exception e) { logWarn(Utils.class, e); } } /** * Checks and removes from the given string all patterns being a word in braces * unless they are listed in supportedVariables
* * e.g.:
* string: [{status}{folder}{dil}]
* supportedVariables: "{status}", "{folder}"
* will result to:
* [{status}{folder}]
* * @param string to be checked string * @param vars supported variables * @return */ public static String skipUnsupportedVariables(String string, String[] supportedVariables) { String ret = string; Pattern p = Pattern.compile("\\{\\w*\\}"); Matcher m = p.matcher(string); while(m.find()) { String g = m.group(); boolean isVar = false; for (String var : supportedVariables) { if(var.equals(g)) { isVar=true; break; } } if(!isVar) { ret = ret.replace(g, ""); } } return ret; } /** * Checks if the context was originally created from files, not from nodes and if so * then it tries to determine if those original files are part of a single DataObject. * Call only if the context was created from files (not from nodes), otherwise always returns false. * * @param ctx context to be checked * @return true if the context was created from files of the same DataObject */ public static boolean isFromMultiFileDataObject (VCSContext ctx) { if (ctx != null) { Collection allSets = ctx.getElements().lookupAll(Set.class); if (allSets != null) { for (Set contextElements : allSets) { // private contract with org.openide.loaders - original files from multifile dataobjects are passed as // org.openide.loaders.DataNode$LazyFilesSet if ("org.openide.loaders.DataNode$LazyFilesSet".equals(contextElements.getClass().getName())) { //NOI18N return true; } } } } return false; } /** * Parses system property value and returns a priority for the given versioning system. * The property should be defined as {@code versioning.versioningSystem.priority}. * @param versioningSystem name of the vcs * @return priority or {@link Integer#MAX_VALUE} as default * @deprecated should not be used any more */ @Deprecated public static Integer getPriority (String versioningSystem) { Integer value = null; String propName = "versioning." + versioningSystem + ".priority"; //NOI18N String sValue = System.getProperty(propName, null); if (sValue != null && !sValue.isEmpty()) { try { value = Integer.parseInt(sValue); if (value <= 0) { value = null; } } catch (NumberFormatException ex) { Logger.getLogger(Utils.class.getName()).log(Level.INFO, "Wrong priority ({0}) value {1}, using default value", new Object[] {propName, sValue}); //NOI18N } } if (value == null) { value = Integer.MAX_VALUE; } return value; } private static File getTempDir (boolean deleteOnExit) { if (tempDir == null) { File tmpDir = new File(System.getProperty("java.io.tmpdir")); // NOI18N for (;;) { File dir = new File(tmpDir, "vcs-" + Long.toString(System.currentTimeMillis())); // NOI18N if (!dir.exists() && dir.mkdirs()) { tempDir = FileUtil.normalizeFile(dir); if (deleteOnExit) { tempDir.deleteOnExit(); } break; } } } return tempDir; } private static class ViewEnv implements CloneableEditorSupport.Env { private final FileObject file; private static final long serialVersionUID = -5788777967029507963L; public ViewEnv(FileObject file) { this.file = file; } @Override public InputStream inputStream() throws IOException { return file.getInputStream(); } @Override public OutputStream outputStream() throws IOException { throw new IOException(); } @Override public Date getTime() { return file.lastModified(); } @Override public String getMimeType() { return file.getMIMEType(); } @Override public void addPropertyChangeListener(PropertyChangeListener l) { } @Override public void removePropertyChangeListener(PropertyChangeListener l) { } @Override public void addVetoableChangeListener(VetoableChangeListener l) { } @Override public void removeVetoableChangeListener(VetoableChangeListener l) { } @Override public boolean isValid() { return file.isValid(); } @Override public boolean isModified() { return false; } @Override public void markModified() throws IOException { throw new IOException(); } @Override public void unmarkModified() { } @Override public CloneableOpenSupport findCloneableOpenSupport() { return null; } } private static class ViewCES extends CloneableEditorSupport { private final String name; private final Charset charset; public ViewCES(Env env, String name, Charset charset) { super(env); this.name = name; this.charset = charset; } @Override protected void loadFromStreamToKit(StyledDocument doc, InputStream stream, EditorKit kit) throws IOException, BadLocationException { kit.read(new InputStreamReader(stream, charset), doc, 0); } @Override protected String messageSave() { return name; } @Override protected String messageName() { return name; } @Override protected String messageToolTip() { return name; } @Override protected String messageOpening() { return name; } @Override protected String messageOpened() { return name; } @Override protected boolean asynchronousOpen() { return false; } } // ----- // Usages logging based on repository URL (for Kenai) private static VCSKenaiAccessor kenaiAccessor; private static final LinkedList loggedRoots = new LinkedList(); private static final List foldersToCheck = new LinkedList(); private static Runnable loggingTask = null; public static void logVCSKenaiUsage(String vcs, String repositoryUrl) { VCSKenaiAccessor kenaiSup = getKenaiAccessor(); if (kenaiSup != null) { kenaiSup.logVcsUsage(vcs, repositoryUrl); } } private static VCSKenaiAccessor getKenaiAccessor() { if (kenaiAccessor == null) { kenaiAccessor = Lookup.getDefault().lookup(VCSKenaiAccessor.class); } return kenaiAccessor; } /* * Makes sure repository of given versioned folder is logged for usage * (if on Kenai). Versioned folders are collected and a task invoked in 2s * to process them. Roots are remembered so no subfolder is processed again * (it's enough to log one usage per repository). Called from annotators so * all user visible repositories are logged. */ public static void addFolderToLog(File folder) { if (!checkFolderLogged(folder, false)) { synchronized(foldersToCheck) { foldersToCheck.add(folder); if (loggingTask == null) { loggingTask = new LogTask(); Utils.postParallel(loggingTask, 2000); } } } } /** * Determines versioning systems that manage files in given context. * * @param ctx VCSContext to examine * @return VersioningSystem systems that manage this context or an empty array if the context is not versioned */ public static VersioningSystem[] getOwners(VCSContext ctx) { Set files = ctx.getRootFiles(); Set owners = new HashSet(); for (File file : files) { VersioningSystem vs = VersioningSupport.getOwner(file); if (vs != null) { owners.add(vs); } } return (VersioningSystem[]) owners.toArray(new VersioningSystem[owners.size()]); } private static class LogTask implements Runnable { @Override public void run() { File[] folders; synchronized (foldersToCheck) { folders = foldersToCheck.toArray(new File[foldersToCheck.size()]); foldersToCheck.clear(); loggingTask = null; } for (File f : folders) { if (!checkFolderLogged(f, false)) { // if other task has not processed the root yet VersioningSystem vs = VersioningSupport.getOwner(f); if (vs != null) { File root = vs.getTopmostManagedAncestor(f); if (root != null) { checkFolderLogged(root, true); // remember the root FileObject rootFO = FileUtil.toFileObject(root); if (rootFO != null) { String url = VersioningQuery.getRemoteLocation(rootFO.toURI()); if (url != null) { Object name = vs.getProperty(VersioningSystem.PROP_DISPLAY_NAME); if (!(name instanceof String)) { name = vs.getClass().getSimpleName(); } logVCSKenaiUsage(name.toString(), url); } } } } } } } } private static boolean checkFolderLogged(File folder, boolean add) { synchronized(loggedRoots) { for (File f : loggedRoots) { String ancestorPath = f.getPath(); String folderPath = folder.getPath(); if (folderPath.startsWith(ancestorPath) && (folderPath.length() == ancestorPath.length() || folderPath.charAt(ancestorPath.length()) == File.separatorChar)) { // folder is the same or subfolder of already logged one return true; } } if (add) { loggedRoots.add(folder); } } return false; } /** * Returns hash value for the given byte array and algoritmus in a hex string form. * @param alg Algoritmus to compute the hash value (see also Appendix A in the * Java Cryptography Architecture API Specification & Reference * for information about standard algorithm names.) * @param bytes byte array * @return hash value as a string * @throws java.security.NoSuchAlgorithmException */ public static String getHash(String alg, byte[] bytes) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance(alg); md5.update(bytes); byte[] md5digest = md5.digest(); String ret = ""; // NOI18N for (int i = 0; i < md5digest.length; i++) { String hex = Integer.toHexString(md5digest[i] & 0x000000FF); if (hex.length() == 1) { hex = "0" + hex; // NOI18N } ret += hex + (i < md5digest.length - 1 ? ":" : ""); // NOI18N } return ret; } /** * Returns files from all opened top components * @return set of opened files */ public static Set getOpenFiles() { TopComponent[] comps = TopComponent.getRegistry().getOpened().toArray(new TopComponent[0]); Set openFiles = new HashSet(comps.length); for (TopComponent tc : comps) { Node[] nodes = tc.getActivatedNodes(); if (nodes == null) { continue; } for (Node node : nodes) { File file = node.getLookup().lookup(File.class); if (file == null) { FileObject fo = node.getLookup().lookup(FileObject.class); if (fo != null && fo.isData()) { file = FileUtil.toFile(fo); } } if (file != null) { openFiles.add(file); } } } return openFiles; } /** * Switches the wait cursor on the NetBeans glasspane of/on * * @param on */ public static void setWaitCursor(final boolean on) { Runnable r = new Runnable() { @Override public void run() { JFrame mainWindow = (JFrame) WindowManager.getDefault().getMainWindow(); mainWindow .getGlassPane() .setCursor(Cursor.getPredefinedCursor( on ? Cursor.WAIT_CURSOR : Cursor.DEFAULT_CURSOR)); mainWindow.getGlassPane().setVisible(on); } }; if(EventQueue.isDispatchThread()) { r.run(); } else { EventQueue.invokeLater(r); } } /** * Returns the {@link Project} {@link File} for the given context * * @param VCSContext * @return File of Project Directory */ public static File getProjectFile(VCSContext context){ return getProjectFile(getProject(context)); } /** * Returns {@link Project} for the given context * * @param context * @return */ public static Project getProject(VCSContext context){ if (context == null) return null; return getProject(context.getRootFiles().toArray(new File[context.getRootFiles().size()])); } public static Project getProject (File[] files) { for (File file : files) { /* We may be committing a LocallyDeleted file */ if (!file.exists()) file = file.getParentFile(); FileObject fo = FileUtil.toFileObject(file); if(fo == null) { LOG.log(Level.FINE, "Utils.getProjectFile(): No FileObject for {0}", file); // NOI18N } else { Project p = FileOwnerQuery.getOwner(fo); if (p != null) { return p; } else { LOG.log(Level.FINE, "Utils.getProjectFile(): No project for {0}", file); // NOI18N } } } return null; } /** * Returns the {@link Project} {@link File} for the given {@link Project} * * @param project * @return */ public static File getProjectFile(Project project){ if (project == null) return null; FileObject fo = project.getProjectDirectory(); return FileUtil.toFile(fo); } /** * Returns all root files for the given {@link Project} * * @param project * @return */ public static File[] getProjectRootFiles(Project project){ if (project == null) return null; Set set = new HashSet(); Sources sources = ProjectUtils.getSources(project); SourceGroup [] sourceGroups = sources.getSourceGroups(Sources.TYPE_GENERIC); for (int j = 0; j < sourceGroups.length; j++) { SourceGroup sourceGroup = sourceGroups[j]; FileObject srcRootFo = sourceGroup.getRootFolder(); File rootFile = FileUtil.toFile(srcRootFo); set.add(rootFile); } return set.toArray(new File[set.size()]); } public static void setAcceleratorBindings(String pathPrefix, Action... actions) { for (Action a : actions) { if(a == null) continue; Action foAction; if(a instanceof SystemActionBridge) { foAction = ((SystemActionBridge) a).getDelegate(); } else { foAction = a; } if(!pathPrefix.endsWith("/")) { // NOI18N pathPrefix += "/"; // NOI18N } FileObject fo = FileUtil.getConfigFile(pathPrefix + foAction.getClass().getName().replaceAll("\\.", "-") + ".instance"); // NOI18N if(fo != null) { AcceleratorBinding.setAccelerator(a, fo); } } } public static Action getAcceleratedAction(String path) { // or use Actions.forID Action a = FileUtil.getConfigObject(path, Action.class); FileObject fo = FileUtil.getConfigFile(path); if(fo != null) { AcceleratorBinding.setAccelerator(a, fo); } return a; } /** * Guesses the line-ending used in the file. * Default OS line-ending is used when file has no newlines. * * @param fo file to get line-ending for. * @param lock file lock * @return */ public static String getLineEnding (FileObject fo, FileLock lock) { if (!lock.isValid()) { throw new IllegalStateException(); } String newLineStr = (String) fo.getAttribute(FileObject.DEFAULT_LINE_SEPARATOR_ATTR); if (newLineStr == null || newLineStr.isEmpty()) { newLineStr = System.getProperty("line.separator"); //NOI18N } try (InputStream is = new BufferedInputStream(fo.getInputStream())) { String lineEnding = getLineEnding(is); if (!lineEnding.isEmpty()) { newLineStr = lineEnding; } } catch (IOException ex) { } return newLineStr; } static String getLineEnding (InputStream is) throws IOException { String newLineStr = ""; byte [] buffer = new byte[1024]; int n; boolean finished = false; List allowed = Arrays.asList(new String[] { "\n", "\r", "\r\n" }); while (!finished && (n = is.read(buffer)) != -1) { for (int i = 0; i < n; ++i) { byte c = buffer[i]; if ((c == '\n' || c == '\r') && allowed.contains(newLineStr + (char) c)) { newLineStr += (char) c; } else if (!newLineStr.isEmpty()) { finished = true; break; } } } return newLineStr; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy