Input:
import frost.storage.FileListCallback;
import frost.storage.perst.filelist.FileListStorage;
import frost.util.TextSearchFun;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import frost.Core;
import frost.SettingsClass;
import frost.fileTransfer.FrostFileListFileObject;
import frost.fileTransfer.FrostFileListFileObjectOwner;
import frost.identities.Identity;

Context:
src/main/java/frost/fileTransfer/search/SearchThread.java
/*
  SearchThread.java / Frost
  Copyright (C) 2001  Frost Project <jtcfrost.sourceforge.net>

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation; either version 2 of
  the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package frost.fileTransfer.search;

class SearchThread extends Thread implements FileListCallback {

	private static final Logger logger = LoggerFactory.getLogger(SearchThread.class);

    private SearchParameters searchParams;

    private String[] audioExtension;
    private String[] videoExtension;
    private String[] imageExtension;
    private String[] documentExtension;
    private String[] executableExtension;
    private String[] archiveExtension;

    private int allFileCount;
    private int maxSearchResults;

    private SearchTable searchTable;

    private boolean isCancelRequested = false;
    private boolean isMaximumSearchResultsReached = false;

    private SearchPanel.ProxyPanel tabComponent;

    private boolean isCancelRequested() {
        return isCancelRequested;
    }
    public void requestCancel() {
        isCancelRequested = true;
    }

    private boolean isMaximumSearchResultsReached() {
        return isMaximumSearchResultsReached;
    }
    public void maximumSearchResultsReached() {
        isMaximumSearchResultsReached = true;
    }

    private String lowerCase(String s) {
        if( s == null ) {
            return "";
        } else {
            return s.toLowerCase();
        }
    }

    /**
     * Check search options, step 1.
     */
    private boolean searchFile1(FrostFileListFileObject fo) {

        // check if file has a key
        if( searchParams.isHideFilesWithoutChkKey() ) {
            if( fo.getKey() == null || fo.getKey().length() == 0 ) {
                return false;
            }
        }

        // hideFiles: show file if no bad/check/observe owner; or if at least 1 owner is good/observe
        if( searchParams.isHideBadUserFiles()
                || searchParams.isHideCheckUserFiles()
                || searchParams.isHideObserveUserFiles())
        {
            boolean accept = true;

src/main/java/frost/Core.java
public class Core {

	private static final Logger logger = LoggerFactory.getLogger(Core.class);

    // Core instanciates itself, frostSettings must be created before instance=Core() !
    public static final SettingsClass frostSettings = new SettingsClass();

    private static Core instance = null;

    private static final FrostCrypt crypto = new FrostCrypt();

    private static boolean isHelpHtmlSecure = false;

    private Language language = null;

    private static boolean freenetIsOnline = false;
    private static boolean freetalkIsTalkable = false;

    private final Timer timer = new Timer(true);

    private MainFrame mainFrame;
    private MessagingManager messagingManager;
    private FileTransferManager fileTransferManager;

    private static IdentitiesManager identitiesManager;

    /**
     *
     */
    private Core() {
        initializeLanguage();
    }

    /**
     * This methods parses the list of available nodes (and converts it if it is in
     * the old format). If there are no available nodes, it shows a Dialog warning the
     * user of the situation and returns false.
     * @return boolean false if no nodes are available. True otherwise.
     */
    private boolean initializeConnectivity() {

        // determine configured freenet version
        final int freenetVersion = frostSettings.getIntValue(SettingsClass.FREENET_VERSION); // only 7 is supported
        if( freenetVersion != 7 ) {
            MiscToolkit.showMessage(
                    language.getString("Core.init.UnsupportedFreenetVersionBody")+": "+freenetVersion,
                    JOptionPane.ERROR_MESSAGE,
                    language.getString("Core.init.UnsupportedFreenetVersionTitle"));
            return false;
        }

        // get the list of available nodes
        String nodesUnparsed = frostSettings.getValue(SettingsClass.FREENET_FCP_ADDRESS);
        if ((nodesUnparsed == null) || (nodesUnparsed.length() == 0)) {
            frostSettings.setValue(SettingsClass.FREENET_FCP_ADDRESS, "127.0.0.1:9481");
            nodesUnparsed = frostSettings.getValue(SettingsClass.FREENET_FCP_ADDRESS);
        }

        final List<String> nodes = new ArrayList<String>();

        // earlier we supported multiple nodes, so check if there is more than one node
        if( nodesUnparsed != null ) {
            final String[] _nodes = nodesUnparsed.split(",");
            for( final String element : _nodes ) {
                nodes.add(element);
            }
        }

        // paranoia, should never happen
        if (nodes.size() == 0) {
            MiscToolkit.showMessage(
                "Not a single Freenet node configured. Frost cannot start.",
                JOptionPane.ERROR_MESSAGE,
                "ERROR: No Freenet nodes are configured.");
            return false;
        }

        if (nodes.size() > 1) {
            MiscToolkit.showMessage(
                    "Frost doesn' support multiple Freenet nodes and will use the first configured node.",
                    JOptionPane.ERROR_MESSAGE,
                    "Warning: Using first configured node");
            frostSettings.setValue(SettingsClass.FREENET_FCP_ADDRESS, nodes.get(0));
        }

        // init the factory with configured node
        try {
            FcpHandler.initializeFcp(nodes.get(0));
        } catch(final Exception ex) {
            MiscToolkit.showMessage(
                    ex.getMessage(),
                    JOptionPane.ERROR_MESSAGE,
                    language.getString("Core.init.UnsupportedFreenetVersionTitle"));
            return false;
        }

        // install our security manager that only allows connections to the configured FCP hosts
        System.setSecurityManager(new FrostSecurityManager());

        // check if node is online and if we run on 0.7 testnet
        setFreenetOnline(false);

        if( Frost.isOfflineMode() ) {
            // keep offline
            return true;
        }

        // We warn the user when he connects to a 0.7 testnet node
        // this also tries to connect to a configured node and sets 'freenetOnline'
        boolean runningOnTestnet = false;
        try {
            final FcpConnection fcpConn = new FcpConnection(FcpHandler.inst().getFreenetNode());
            final NodeMessage nodeMessage = fcpConn.getNodeInfo();

            // node answered, freenet is online
            setFreenetOnline(true);

            if (nodeMessage.getBoolValue("Testnet")) {
                runningOnTestnet = true;
            }

            final boolean freetalkTalkable = fcpConn.checkFreetalkPlugin();
            setFreetalkTalkable (freetalkTalkable);

            if (freetalkTalkable) {
                logger.info("**** Freetalk is Talkable. ****");
            } else {
                logger.warn("**** Freetalk is NOT Talkable. ****");
            }

            fcpConn.close();

        } catch (final Exception e) {
            logger.error("Exception thrown in initializeConnectivity", e);
        }

        if (runningOnTestnet) {
            MiscToolkit.showMessage(
                    language.getString("Core.init.TestnetWarningBody"),
                    JOptionPane.WARNING_MESSAGE,
                    language.getString("Core.init.TestnetWarningTitle"));
        }

        // We warn the user if there aren't any running nodes
        if (!isFreenetOnline()) {
            MiscToolkit.showMessage(
                language.getString("Core.init.NodeNotRunningBody"),
                JOptionPane.WARNING_MESSAGE,
                language.getString("Core.init.NodeNotRunningTitle"));
        } else {
            // maybe start a single message connection
            FcpHandler.inst().goneOnline();
        }

        if (!frostSettings.getBoolValue(SettingsClass.FILESHARING_DISABLE)) {
            MiscToolkit.showSuppressableConfirmDialog(
                    MainFrame.getInstance(),
                    language.getString("Core.init.FileSharingEnabledBody"),
                    language.getString("Core.init.FileSharingEnabledTitle"),
                    JOptionPane.OK_OPTION,
                    JOptionPane.WARNING_MESSAGE,
                    SettingsClass.CONFIRM_FILESHARING_IS_ENABLED,
                    language.getString("Common.suppressConfirmationCheckbox") );
        }

        return true;
    }

    public static void setFreenetOnline(final boolean v) {
        freenetIsOnline = v;
    }
    public static boolean isFreenetOnline() {
        return freenetIsOnline;
    }

    public static void setFreetalkTalkable(final boolean v) {
        freetalkIsTalkable = v;
    }
    public static boolean isFreetalkTalkable() {
        return freetalkIsTalkable;
    }

    public static FrostCrypt getCrypto() {
        return crypto;
    }

    public static void schedule(final TimerTask task, final long delay) {
        getInstance().timer.schedule(task, delay);
    }

    public static void schedule(final TimerTask task, final long delay, final long period) {
        getInstance().timer.schedule(task, delay, period);
    }

    /**
     * @return pointer to the live core
     */
    public static Core getInstance() {
        if( instance == null ) {
            instance = new Core();
        }
        return instance;
    }

    private void showFirstStartupDialog() {
        // clean startup, ask user which freenet version to use, set correct default availableNodes
        final FirstStartupDialog startdlg = new FirstStartupDialog();
        final boolean exitChoosed = startdlg.startDialog();
        if( exitChoosed ) {
            System.exit(1);
        }

        // first startup, no migrate needed
        frostSettings.setValue(SettingsClass.MIGRATE_VERSION, 3);

        // set used version
        final int freenetVersion = 7;
        frostSettings.setValue(SettingsClass.FREENET_VERSION, freenetVersion);
        // init availableNodes with correct port
        if( startdlg.getOwnHostAndPort() != null ) {
            // user set own host:port
            frostSettings.setValue(SettingsClass.FREENET_FCP_ADDRESS, startdlg.getOwnHostAndPort());
        } else {
            // 0.7 darknet
            frostSettings.setValue(SettingsClass.FREENET_FCP_ADDRESS, "127.0.0.1:9481");
        }
    }

    private void compactPerstStorages(final Splashscreen splashscreen) throws Exception {
        try {
            long savedBytes = 0;
            savedBytes += compactStorage(splashscreen, IndexSlotsStorage.inst());
            savedBytes += compactStorage(splashscreen, FrostFilesStorage.inst());
            savedBytes += compactStorage(splashscreen, IdentitiesStorage.inst());
            savedBytes += compactStorage(splashscreen, SharedFilesCHKKeyStorage.inst());
            savedBytes += compactStorage(splashscreen, MessageStorage.inst());
            savedBytes += compactStorage(splashscreen, MessageContentStorage.inst());
            savedBytes += compactStorage(splashscreen, FileListStorage.inst());
            savedBytes += compactStorage(splashscreen, ArchiveMessageStorage.inst());

            final NumberFormat nf = NumberFormat.getInstance();
            logger.info("Finished compact of storages, released {} bytes.", nf.format(savedBytes));
        } catch(final Exception ex) {
            logger.error("Error compacting perst storages", ex);
            MiscToolkit.showMessage(
                    "Error compacting perst storages, compact did not complete: "+ex.getMessage(),
                    JOptionPane.ERROR_MESSAGE,
                    "Error compacting perst storages");
            throw ex;
        }
    }

    private long compactStorage(final Splashscreen splashscreen, final AbstractFrostStorage storage) throws Exception {
        splashscreen.setText("Compacting storage file '"+storage.getStorageFilename()+"'...");
        return storage.compactStorage();
    }

    private void exportStoragesToXml(final Splashscreen splashscreen) throws Exception {
        try {
            exportStorage(splashscreen, IndexSlotsStorage.inst());
            exportStorage(splashscreen, FrostFilesStorage.inst());
            exportStorage(splashscreen, IdentitiesStorage.inst());
            exportStorage(splashscreen, SharedFilesCHKKeyStorage.inst());
            exportStorage(splashscreen, MessageStorage.inst());
            exportStorage(splashscreen, MessageContentStorage.inst());
            exportStorage(splashscreen, FileListStorage.inst());
            exportStorage(splashscreen, ArchiveMessageStorage.inst());
            logger.info("Finished export to XML");
        } catch(final Exception ex) {
            logger.error("Error exporting perst storages", ex);
            MiscToolkit.showMessage(
                    "Error exporting perst storages, export did not complete: "+ex.getMessage(),
                    JOptionPane.ERROR_MESSAGE,
            "Error exporting perst storages");
            throw ex;
        }
    }

    private void exportStorage(final Splashscreen splashscreen, final AbstractFrostStorage storage) throws Exception {
        splashscreen.setText("Exporting storage file '"+storage.getStorageFilename()+"'...");
        storage.exportToXml();
    }

    /**
     * Initialize, show splashscreen.
     */
    public void initialize() throws Exception {

        final Splashscreen splashscreen = new Splashscreen(frostSettings.getBoolValue(SettingsClass.DISABLE_SPLASHSCREEN));
        splashscreen.setVisible(true);

        splashscreen.setText(language.getString("Splashscreen.message.1"));
        splashscreen.setProgress(20);

        // CLEANS TEMP DIR! START NO INSERTS BEFORE THIS DID RUN
        Startup.startupCheck(frostSettings);

        // if first startup ask user for freenet version to use
        if( frostSettings.getIntValue(SettingsClass.FREENET_VERSION) == 0 ) {
            showFirstStartupDialog();
        }

        // we must be at migration level 2 (no mckoi)!!!
        if( frostSettings.getIntValue(SettingsClass.MIGRATE_VERSION) < 2 ) {
            logger.error("You must update this Frost version from version 11-Dec-2007 !!!");
            System.exit(8);
        }

        // before opening the storages, maybe compact them
        if( frostSettings.getBoolValue(SettingsClass.PERST_COMPACT_STORAGES) ) {
            compactPerstStorages(splashscreen);
            frostSettings.setValue(SettingsClass.PERST_COMPACT_STORAGES, false);
        }

        // one time: change cleanup settings to new default, they were way to high
        if( frostSettings.getIntValue(SettingsClass.MIGRATE_VERSION) < 3 ) {
            frostSettings.setValue(SettingsClass.DB_CLEANUP_REMOVEOFFLINEFILEWITHKEY, true);
            if (frostSettings.getIntValue(SettingsClass.DB_CLEANUP_OFFLINEFILESMAXDAYSOLD) > 30) {
                frostSettings.setValue(SettingsClass.DB_CLEANUP_OFFLINEFILESMAXDAYSOLD, 30);
            }

            // run cleanup now
            frostSettings.setValue(SettingsClass.DB_CLEANUP_LASTRUN, 0L);
            // run compact during next startup (after the cleanup)
            frostSettings.setValue(SettingsClass.PERST_COMPACT_STORAGES, true);
            // migration is done
            frostSettings.setValue(SettingsClass.MIGRATE_VERSION, 3);
        }

        // maybe export perst storages to XML
        if( frostSettings.getBoolValue(SettingsClass.PERST_EXPORT_STORAGES) ) {
            exportStoragesToXml(splashscreen);
            frostSettings.setValue(SettingsClass.PERST_EXPORT_STORAGES, false);
        }

        // initialize perst storages
        IndexSlotsStorage.inst().initStorage();
        SharedFilesCHKKeyStorage.inst().initStorage();
        FrostFilesStorage.inst().initStorage();
        MessageStorage.inst().initStorage();
        MessageContentStorage.inst().initStorage();
        ArchiveMessageStorage.inst().initStorage();
        IdentitiesStorage.inst().initStorage();
        FileListStorage.inst().initStorage();
        TrackDownloadKeysStorage.inst().initStorage();

        splashscreen.setText(language.getString("Splashscreen.message.2"));
        splashscreen.setProgress(40);

        // check if help.zip contains only secure files (no http or ftp links at all)
        {
            final CheckHtmlIntegrity chi = new CheckHtmlIntegrity();
            isHelpHtmlSecure = chi.scanZipFile("help/help.zip");
        }

        splashscreen.setText(language.getString("Splashscreen.message.3"));
        splashscreen.setProgress(60);

        // sets the freenet version, initializes identities
        if (!initializeConnectivity()) {
            System.exit(1);
        }

        getIdentitiesManager().initialize();

        String title = "Frost";

        if( !isFreenetOnline() ) {
            title += " (offline mode)";
        }

        // Main frame
        mainFrame = new MainFrame(frostSettings, title);
        getMessagingManager().initialize();

        getFileTransferManager().initialize();
        UnsentMessagesManager.initialize();

        if (frostSettings.getBoolValue(SettingsClass.FREETALK_SHOW_TAB)) {
            FreetalkManager.initialize();
        }

        splashscreen.setText(language.getString("Splashscreen.message.4"));
        splashscreen.setProgress(70);

        // Display the tray icon (do this before mainframe initializes)
        if ((frostSettings.getBoolValue(SettingsClass.SHOW_SYSTRAY_ICON) == true) && SystraySupport.isSupported()) {
            try {
                if (!SystraySupport.initialize(title)) {
                    logger.error("Could not create systray icon.");
                }
            } catch(final Throwable t) {
                logger.error("Could not create systray icon.", t);
            }
        }

        mainFrame.initialize();

        // cleanup gets the expiration mode from settings
        CleanUp.runExpirationTasks(splashscreen, MainFrame.getInstance().getMessagingTab().getTofTreeModel().getAllBoards());

        // Show enqueued startup messages before showing the mainframe,
        // otherwise the glasspane used during load of board messages could corrupt the modal message dialog!
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                mainFrame.showStartupMessages();
            }
        });

        // After expiration, select previously selected board tree row.
        // NOTE: This loads the message table!!!
        mainFrame.postInitialize();

        splashscreen.setText(language.getString("Splashscreen.message.5"));
        splashscreen.setProgress(80);

        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                mainFrame.setVisible(true);
            }
        });

        splashscreen.closeMe();

        // boot up the machinery ;)
        initializeTasks(mainFrame);
    }

    /**
     * @return
     */
    public FileTransferManager getFileTransferManager() {
        if (fileTransferManager == null) {
            fileTransferManager = FileTransferManager.inst();
        }
        return fileTransferManager;
    }

    /**
     * @return
     */
    private MessagingManager getMessagingManager() {
        if (messagingManager == null) {
            messagingManager = new MessagingManager(frostSettings, mainFrame);
        }
        return messagingManager;
    }

    /**
     * @param parentFrame the frame that will be the parent of any
     *          dialog that has to be shown in case an error happens
     *          in one of those tasks
     */
    private void initializeTasks(final MainFrame mainframe) {
        // initialize the task that frees memory
        TimerTask cleaner = new TimerTask() {
            @Override
            public void run() {
                logger.info("freeing memory");
                System.gc();
            }
        };
        final long gcMinutes = 10;
        timer.schedule(cleaner, gcMinutes * 60L * 1000L, gcMinutes * 60L * 1000L);
        cleaner = null;

        // initialize the task that saves data
        final StorageManager saver = new StorageManager(frostSettings);

        // auto savables
        saver.addAutoSavable(getMessagingManager().getTofTree());
        saver.addAutoSavable(getFileTransferManager());
        saver.addAutoSavable(new IdentityAutoBackupTask());

        // exit savables, must run before the perst storages are closed
        saver.addExitSavable(new IdentityAutoBackupTask());
        saver.addExitSavable(getMessagingManager().getTofTree());
        saver.addExitSavable(getFileTransferManager());

        saver.addExitSavable(frostSettings);

        // close perst Storages
        saver.addExitSavable(IndexSlotsStorage.inst());
        saver.addExitSavable(SharedFilesCHKKeyStorage.inst());
        saver.addExitSavable(FrostFilesStorage.inst());
        saver.addExitSavable(MessageStorage.inst());
        saver.addExitSavable(MessageContentStorage.inst());
        saver.addExitSavable(ArchiveMessageStorage.inst());
        saver.addExitSavable(IdentitiesStorage.inst());
        saver.addExitSavable(FileListStorage.inst());
        saver.addExitSavable(TrackDownloadKeysStorage.inst());

        // invoke the mainframe ticker (board updates, clock, ...)
        mainframe.startTickerThread();

        // start file attachment uploads
        FileAttachmentUploadThread.getInstance().start();

        // start all filetransfer tickers
        getFileTransferManager().startTickers();

        // after X seconds, start filesharing threads if enabled
        if( isFreenetOnline() && !frostSettings.getBoolValue(SettingsClass.FILESHARING_DISABLE)) {
            final Thread t = new Thread() {
                @Override
                public void run() {
                    Mixed.wait(10000);
                    FileSharingManager.startFileSharing();
                }
            };
            t.start();
        }
    }

    /**
     * @return
     */
    public static IdentitiesManager getIdentitiesManager() {
        if (identitiesManager == null) {
            identitiesManager = new IdentitiesManager();
        }
        return identitiesManager;
    }

    /**
     * This method returns the language resource to get internationalized messages
     * from. That language resource is initialized the first time this method is called.
     * In that case, if the locale field has a value, it is used to select the
     * LanguageResource. If not, the locale value in frostSettings is used for that.
     */
    private void initializeLanguage() {
        if( Frost.getCmdLineLocaleFileName() != null ) {
            // external bundle specified on command line (overrides config setting)
            final File f = new File(Frost.getCmdLineLocaleFileName());
            Language.initializeWithFile(f);
        } else if (Frost.getCmdLineLocaleName() != null) {
            // use locale specified on command line (overrides config setting)
            Language.initializeWithName(Frost.getCmdLineLocaleName());
        } else {
            // use config file parameter (format: de or de;ext
            final String lang = frostSettings.getValue(SettingsClass.LANGUAGE_LOCALE);
            final String langIsExternal = frostSettings.getValue("localeExternal");
            if( (lang == null) || (lang.length() == 0) || lang.equals("default") ) {
                // for default or if not set at all
                frostSettings.setValue(SettingsClass.LANGUAGE_LOCALE, "default");
                Language.initializeWithName(null);
            } else {
                boolean isExternal;
                if( (langIsExternal == null) || (langIsExternal.length() == 0) || !langIsExternal.equals("true")) {
                    isExternal = false;
                } else {
                    isExternal = true;
                }
                Language.initializeWithName(lang, isExternal);
            }
        }
        language = Language.getInstance();
    }

    public void showAutoSaveError(final Exception exception) {
        final StringWriter stringWriter = new StringWriter();
        exception.printStackTrace(new PrintWriter(stringWriter));

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                if (mainFrame != null) {
                    JDialogWithDetails.showErrorDialog(
                            mainFrame,
                            language.getString("Saver.AutoTask.title"),
                            language.getString("Saver.AutoTask.message"),
                            stringWriter.toString());
                    System.exit(3);
                }
            }
        });
    }

    public static boolean isHelpHtmlSecure() {
        return isHelpHtmlSecure;
    }
}

src/main/java/frost/identities/Identity.java
public class Identity extends Persistent implements XMLizable {

	private static final Logger logger = LoggerFactory.getLogger(Identity.class);

    private static transient final int GOOD    = 1;
    private static transient final int CHECK   = 2;
    private static transient final int OBSERVE = 3;
    private static transient final int BAD     = 4;

    private static transient final String GOOD_STRING    = "GOOD";
    private static transient final String CHECK_STRING   = "CHECK";
    private static transient final String OBSERVE_STRING = "OBSERVE";
    private static transient final String BAD_STRING     = "BAD";

    private String uniqueName;
    private long lastSeenTimestamp = -1;
    private int receivedMessageCount = 0;
    private String comment = "";

    private int state = CHECK;
    private transient String stateString = CHECK_STRING;

    private transient String publicKey;

    private PerstIdentityPublicKey pPublicKey;

    /**
     * @param el
     * @throws Exception
     */
    protected Identity(final Element el) throws Exception {
        try {
            loadXMLElement(el);
        } catch (final SAXException e) {
            logger.error("Exception thrown in constructor", e);
        }
    }

    /**
     * we use this constructor whenever we have all the info
     *
     * @param name
     * @param key
     */
    private Identity(final String name, final String key) {
        this.publicKey = key;
        this.uniqueName = name;
    }

    /**
     * If a LocalIdentity is deleted, we ceate a GOOD Identity for the deleted LocalIdentity
     *
     * @param li
     */
    public Identity(final LocalIdentity li) {
        uniqueName = li.getUniqueName();
        publicKey = li.getPublicKey();
        lastSeenTimestamp = li.getLastSeenTimestamp();
        receivedMessageCount = li.getReceivedMessageCount();
        comment = li.getComment();
        updateStateString();
    }

    /**
     * Create a new Identity from the specified uniqueName and publicKey.
     * If uniqueName does not contain an '@', this method creates a new digest
     * for the publicKey and appens it to the uniqueName.
     * Finally Mixed.makeFilename() is called for the uniqueName.
     *
     * @param name
     * @param key
     * @param createNew
     */
    protected Identity(String name, final String key, final boolean createNew) {
        if( name.indexOf("@") < 0 ) {
            name = name + "@" + Core.getCrypto().digest(key);
        }
        name = Mixed.makeFilename(name);

        this.publicKey = key;
        this.uniqueName = name;
    }

    ////////////////////////////////////////////////////////////////////
    // FACTORY METHODS /////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////

    /**
     * Create a new Identity from the specified uniqueName and publicKey.
     * Does not convert the specified uniqueName using Mixed.makeFilename() !
     *
     * @return null if the Identity cannot be created
     */
    public static Identity createIdentityFromExactStrings(final String name, final String key) {
        return new Identity(name, key);
    }

    /**
     * Create a new Identity, read from the specified XML element.
     * Calls Mixed.makeFilename() on read uniqueName.
     *
     * @param el  the XML element containing the Identity information
     * @return    the new Identity, or null if Identity cannot be created (invalid input)
     */
    public static Identity createIdentityFromXmlElement(final Element el) {
        try {
            return new Identity(el);
        } catch (final Exception e) {
            return null;
        }
    }

    ////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////

    @Override
    public boolean recursiveLoading() {
        return false;
    }

    @Override
    public void deallocate() {
        if( pPublicKey != null ) {
            pPublicKey.deallocate();
            pPublicKey = null;
        }
        super.deallocate();
    }

    class PerstIdentityPublicKey extends Persistent {
        private String perstPublicKey;
        public PerstIdentityPublicKey() {}
        public PerstIdentityPublicKey(final String pk) {
            perstPublicKey = pk;
        }
        public String getPublicKey() {
            return perstPublicKey;
        }

		public void onLoad() {
			logger.debug("load pubkey");
		}

        @Override
        public boolean recursiveLoading() {
            return false; // load publicKey on demand
        }
    }

    @Override
    public void onStore() {
        if( pPublicKey == null && publicKey != null ) {
            // link public key
            pPublicKey = new PerstIdentityPublicKey(publicKey);
        }
    }

    @Override
    public void onLoad() {
        updateStateString();
    }

    public Element getXMLElement(final Document doc)  {

        final Element el = doc.createElement("Identity");

        Element element = doc.createElement("name");
        CDATASection cdata = doc.createCDATASection(getUniqueName());
        element.appendChild( cdata );
        el.appendChild( element );

        element = doc.createElement("key");
        cdata = doc.createCDATASection(getPublicKey());
        element.appendChild( cdata );
        el.appendChild( element );

        return el;
    }

    @Deprecated
    public Element getXMLElement_old(final Document doc)  {

        final Element el = doc.createElement("MyIdentity");

        Element element = doc.createElement("name");
        CDATASection cdata = doc.createCDATASection(getUniqueName());
        element.appendChild( cdata );
        el.appendChild( element );

        element = doc.createElement("key");
        cdata = doc.createCDATASection(getPublicKey());
        element.appendChild( cdata );
        el.appendChild( element );

        return el;
    }

    public Element getExportXMLElement(final Document doc)  {
        final Element el = getXMLElement(doc);

        if( getLastSeenTimestamp() > -1 ) {
            final Element element = doc.createElement("lastSeen");
            final Text txt = doc.createTextNode(Long.toString(getLastSeenTimestamp()));
            element.appendChild( txt );
            el.appendChild( element );
        }
        if( getReceivedMessageCount() > -1 ) {
            final Element element = doc.createElement("messageCount");
            final Text txt = doc.createTextNode(Long.toString(getReceivedMessageCount()));
            element.appendChild( txt );
            el.appendChild( element );
        }
        return el;
    }

    public void loadXMLElement(final Element e) throws SAXException {
        uniqueName = XMLTools.getChildElementsCDATAValue(e, "name");
        publicKey =  XMLTools.getChildElementsCDATAValue(e, "key");

        String lastSeenStr = XMLTools.getChildElementsTextValue(e,"lastSeen");
        if( lastSeenStr != null && ((lastSeenStr=lastSeenStr.trim())).length() > 0 ) {
            lastSeenTimestamp = Long.parseLong(lastSeenStr);
        } else {
            // not yet set, init with current timestamp
            lastSeenTimestamp = System.currentTimeMillis();
        }

        String msgCount = XMLTools.getChildElementsTextValue(e,"messageCount");
        if( msgCount != null && ((msgCount=msgCount.trim())).length() > 0 ) {
            receivedMessageCount = Integer.parseInt(msgCount);
        } else {
            receivedMessageCount = 0;
        }
//        uniqueName = Mixed.makeFilename(uniqueName);
    }

    /**
     * @return  the public key of this Identity
     */
    public String getPublicKey() {
        if( publicKey == null && pPublicKey != null ) {
            pPublicKey.load();
            return pPublicKey.getPublicKey();
        }
        return publicKey;
    }

    public String getUniqueName() {
        return uniqueName;
    }

    public void correctUniqueName() {
        uniqueName = Mixed.makeFilename(uniqueName);
    }

    // dont't store BoardAttachment with pubKey=SSK@...
    public static boolean isForbiddenBoardAttachment(final BoardAttachment ba) {
        if( ba != null &&
            ba.getBoardObj().getPublicKey() != null &&
            ba.getBoardObj().getPublicKey().startsWith("SSK@") )
        {
            return true; // let delete SSK pubKey board
        } else {
            return false;
        }
    }

    public long getLastSeenTimestamp() {
        return lastSeenTimestamp;
    }

    public void setLastSeenTimestamp(final long v) {
        lastSeenTimestamp = v;
        updateIdentitiesStorage();
    }

    public void setLastSeenTimestampWithoutUpdate(final long v) {
        lastSeenTimestamp = v;
    }

    public int getState() {
        return state;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
        updateIdentitiesStorage();
    }

    @Override
    public String toString() {
        return getUniqueName();
    }

    public boolean isGOOD() {
        return state==GOOD;
    }
    public boolean isCHECK() {
        return state==CHECK;
    }
    public boolean isOBSERVE() {
        return state==OBSERVE;
    }
    public boolean isBAD() {
        return state==BAD;
    }

    public void setGOOD() {
        state=GOOD;
        updateStateString();
        updateIdentitiesStorage();
    }
    public void setCHECK() {
        state=CHECK;
        updateStateString();
        updateIdentitiesStorage();
    }
    public void setOBSERVE() {
        state=OBSERVE;
        updateStateString();
        updateIdentitiesStorage();
    }
    public void setBAD() {
        state=BAD;
        updateStateString();
        updateIdentitiesStorage();
    }

    public void setGOODWithoutUpdate() {
        state=GOOD;
        updateStateString();
    }
    public void setCHECKWithoutUpdate() {
        state=CHECK;
        updateStateString();
    }
    public void setOBSERVEWithoutUpdate() {
        state=OBSERVE;
        updateStateString();
    }
    public void setBADWithoutUpdate() {
        state=BAD;
        updateStateString();
    }

    private void updateStateString() {
        if( isCHECK() ) {
            stateString = CHECK_STRING;
        } else if( isOBSERVE() ) {
            stateString = OBSERVE_STRING;
        } else if( isGOOD() ) {
            stateString = GOOD_STRING;
        } else if( isBAD() ) {
            stateString = BAD_STRING;
        } else {
            stateString = "*ERR*";
        }
    }

    public String getStateString() {
        return stateString;
    }

    protected boolean updateIdentitiesStorage() {
        if( !IdentitiesStorage.inst().beginExclusiveThreadTransaction() ) {
            return false;
        }
        modify();
        IdentitiesStorage.inst().endThreadTransaction();
        return true;
    }

    public int getReceivedMessageCount() {
        return receivedMessageCount;
    }

    public void setReceivedMessageCount(final int i) {
        receivedMessageCount = i;
    }

    public void incReceivedMessageCount() {
        this.receivedMessageCount++;
        updateIdentitiesStorage();
    }
}

src/main/java/frost/fileTransfer/FrostFileListFileObject.java
public class FrostFileListFileObject extends Persistent {

	private static final Logger logger = LoggerFactory.getLogger(FrostFileListFileObject.class);

    private String sha = null;  // SHA of the file
    private long size = 0;      // Filesize
    private String key = null;  // CHK key

    private long lastDownloaded = 0;
    private long lastUploaded = 0;
    private long firstReceived = 0;
    private long lastReceived = 0;

    private long requestLastReceived = 0;  // time when we received the last request for this sha
    private int requestsReceivedCount = 0; // received requests count

    private long requestLastSent = 0;      // time when we sent the last request for this file
    private int requestsSentCount = 0;     // sent requests count

    private boolean isHidden = false;      // user can hide files in search panel

    private IPersistentList<FrostFileListFileObjectOwner> frostFileListFileObjectOwnerList;

    // non-persistent fields
    private transient String displayName = null;
    private transient String displayComment = null;
    private transient String displayKeywords = null;
    private transient int displayRating = -1;
    private transient Boolean hasInfosFromMultipleSources = null;

    private transient List<FrostDownloadItem> listeners;

    /**
     * Used if item is loaded from database.
     */
    public FrostFileListFileObject(
            final String newSha1,
            final long newSize,
            final String newKey,
            final long newLastDownloaded,
            final long newLastUploaded,
            final long newFirstReceived,
            final long newLastReceived,
            final long newRequestLastReceived,
            final int newRequestsReceivedCount,
            final long newRequestLastSent,
            final int newRequestSentCount)
    {
        sha = newSha1;
        size = newSize;
        key = newKey;
        lastDownloaded = newLastDownloaded;
        lastUploaded = newLastUploaded;
        firstReceived = newFirstReceived;
        lastReceived = newLastReceived;
        requestLastReceived = newRequestLastReceived;
        requestsReceivedCount = newRequestsReceivedCount;
        requestLastSent = newRequestLastSent;
        requestsSentCount = newRequestSentCount;
    }

    /**
     * Create instance with data from SharedFilesXmlFile.
     * After creation this item should only be saved to database,
     * this merges the data from this item in case this files is
     * already in the filelist (adds new owner/board).
     */
    public FrostFileListFileObject(final SharedFileXmlFile sfo, final Identity owner, final long timestamp) {

        sha = sfo.getSha();
        size = sfo.getSize().longValue();
        key = sfo.getKey();
        lastDownloaded = 0;
        firstReceived = timestamp;
        lastReceived = timestamp; // set or updated after add

        long lastUploadDate = 0;
        if( sfo.getKey() != null ) {
            if( sfo.getLastUploaded() != null ) {
                try {
					final OffsetDateTime dt = DateFun.parseDate(sfo.getLastUploaded(), DateFun.FORMAT_DATE,
							DateFun.getTimeZone());
					lastUploadDate = DateFun.toMilli(dt);
                } catch(final Throwable t) {
                    logger.error("error parsing file last uploaded date", t);
                }
            }
        }
        lastUploaded = lastUploadDate;

        final FrostFileListFileObjectOwner ob = new FrostFileListFileObjectOwner(
                sfo.getFilename(),
                owner.getUniqueName(),
                sfo.getComment(),
                sfo.getKeywords(),
                sfo.getRating(),
                timestamp,
                lastUploadDate,
                sfo.getKey());

        addFrostFileListFileObjectOwner(ob);
    }

    private IPersistentList<FrostFileListFileObjectOwner> getFrostFileListFileObjectOwnerList() {
        if( frostFileListFileObjectOwnerList == null ) {
            frostFileListFileObjectOwnerList = FileListStorage.inst().createList(); // ATTN: is used without store also!
        }
        return frostFileListFileObjectOwnerList;
    }

    public void addFrostFileListFileObjectOwner(final FrostFileListFileObjectOwner v) {
        v.setFileListFileObject(this);
        getFrostFileListFileObjectOwnerList().add(v);
    }

    public void deleteFrostFileListFileObjectOwner(final FrostFileListFileObjectOwner v) {
        getFrostFileListFileObjectOwnerList().remove(v);
    }

    public Iterator<FrostFileListFileObjectOwner> getFrostFileListFileObjectOwnerIterator() {
        return getFrostFileListFileObjectOwnerList().iterator();
    }

    public int getFrostFileListFileObjectOwnerListSize() {
        return getFrostFileListFileObjectOwnerList().size();
    }

    public String getKey() {
        return key;
    }

    public void setKey(final String key) {
        this.key = key;
        notifyListeners();
    }

    public long getLastDownloaded() {
        return lastDownloaded;
    }

    public void setLastDownloaded(final long lastDownloaded) {
        this.lastDownloaded = lastDownloaded;
    }

    public long getLastUploaded() {
        return lastUploaded;
    }

    public void setLastUploaded(final long lastUploaded) {
        this.lastUploaded = lastUploaded;
        notifyListeners();
    }

    public long getLastReceived() {
        return lastReceived;
    }

    public void setLastReceived(final long lastReceived) {
        this.lastReceived = lastReceived;
        notifyListeners();
    }

    public String getSha() {
        return sha;
    }

    public long getSize() {
        return size;
    }

    public long getRequestLastReceived() {
        return requestLastReceived;
    }

    public void setRequestLastReceived(final long requestLastReceived) {
        this.requestLastReceived = requestLastReceived;
        notifyListeners();
    }

    public long getRequestLastSent() {
        return requestLastSent;
    }

    public void setRequestLastSent(final long requestLastSent) {
        this.requestLastSent = requestLastSent;
        notifyListeners();
    }

    public int getRequestsReceivedCount() {
        return requestsReceivedCount;
    }

    public void setRequestsReceivedCount(final int requestsReceivedCount) {
        this.requestsReceivedCount = requestsReceivedCount;
    }

    public int getRequestsSentCount() {
        return requestsSentCount;
    }

    public void setRequestsSentCount(final int requestsSentCount) {
        this.requestsSentCount = requestsSentCount;
    }

    public long getFirstReceived() {
        return firstReceived;
    }
    public void setFirstReceived(final long v) {
        firstReceived = v;
    }

    public boolean isHidden() {
        return isHidden;
    }
    public void setHidden(final boolean isHidden) {
        this.isHidden = isHidden;
    }

    static class MutableInt {
        public int i = 0;
        public String name = "";
        public int rating = 0;
    }

    private static MutableInt defaultMutableInt = new MutableInt();

    public String getDisplayName() {
        if( displayName == null ) {
            final List<FrostFileListFileObjectOwner> lst = getFrostFileListFileObjectOwnerList();
            if( lst == null || lst.size() == 0 ) {
                displayName = "(no sources)";
            } else {
                // choose most often used name
                final Hashtable<String,MutableInt> ht = new Hashtable<String,MutableInt>();
                for( final FrostFileListFileObjectOwner e : lst ) {
                    MutableInt mi = ht.get( e.getName() );
                    if( mi == null ) {
                        mi = new MutableInt();
                        mi.name = e.getName();
                        mi.i = 1;
                        ht.put(e.getName(), mi);
                    } else {
                        mi.i++;
                    }
                }
                MutableInt bestMi = defaultMutableInt;
                for( final MutableInt mi : ht.values() ) {
                    if( mi.i > bestMi.i ) {
                        bestMi = mi;
                    }
                }
                displayName = bestMi.name;
            }
        }
        return displayName;
    }

    /**
     * @return  true if this file has infos from multiple sources (comments, ratings, keywords)
     */
    public Boolean hasInfosFromMultipleSources() {
        if( hasInfosFromMultipleSources == null ) {
            if( getFrostFileListFileObjectOwnerList().size() > 1 ) {
                int valuesCount = 0;
                for( final FrostFileListFileObjectOwner o : getFrostFileListFileObjectOwnerList() ) {
                    // valuesCount is increased by 1 per FrostFileListFileObjectOwner
                    if( o.getComment() != null && o.getComment().length() > 0 ) {
                        valuesCount++;
                    } else if( o.getKeywords() != null && o.getKeywords().length() > 0 ) {
                        valuesCount++;
                    } else if( o.getRating() > 0 ) {
                        valuesCount++;
                    }
                    // if valuesCount is greater 1 we have at least 2 sources that provide informations
                    if( valuesCount > 1 ) {
                        hasInfosFromMultipleSources = Boolean.TRUE;
                        break;
                    }
                }
                if( hasInfosFromMultipleSources == null ) {
                    hasInfosFromMultipleSources = Boolean.FALSE;
                }
            } else {
                hasInfosFromMultipleSources = Boolean.FALSE;
            }
        }
        return hasInfosFromMultipleSources;
    }

    public String getDisplayComment() {
        if( displayComment == null ) {
            final List<FrostFileListFileObjectOwner> lst = getFrostFileListFileObjectOwnerList();
            if( lst == null || lst.size() == 0 ) {
                displayComment = "(no sources)";
            } else {
                // choose most often used name
                final Hashtable<String,MutableInt> ht = new Hashtable<String,MutableInt>();
                for( final FrostFileListFileObjectOwner e : lst ) {
                    final String c = e.getComment();
                    if( c == null || c.length() == 0 ) {
                        continue;
                    }
                    MutableInt mi = ht.get( c );
                    if( mi == null ) {
                        mi = new MutableInt();
                        mi.name = c;
                        mi.i = 1;
                        ht.put(c, mi);
                    } else {
                        mi.i++;
                    }
                }
                MutableInt bestMi = defaultMutableInt;
                for( final MutableInt mi : ht.values() ) {
                    if( mi.i > bestMi.i ) {
                        bestMi = mi;
                    }
                }
                displayComment = bestMi.name;
            }
        }
        return displayComment;
    }

    public String getDisplayKeywords() {
        if( displayKeywords == null ) {
            final List<FrostFileListFileObjectOwner> lst = getFrostFileListFileObjectOwnerList();
            if( lst == null || lst.size() == 0 ) {
                displayKeywords = "(no sources)";
            } else {
                // choose most often used name
                final Hashtable<String,MutableInt> ht = new Hashtable<String,MutableInt>();
                for( final FrostFileListFileObjectOwner e : lst ) {
                    final String c = e.getKeywords();
                    if( c == null || c.length() == 0 ) {
                        continue;
                    }
                    MutableInt mi = ht.get( c );
                    if( mi == null ) {
                        mi = new MutableInt();
                        mi.name = c;
                        mi.i = 1;
                        ht.put(c, mi);
                    } else {
                        mi.i++;
                    }
                }
                MutableInt bestMi = defaultMutableInt;
                for( final MutableInt mi : ht.values() ) {
                    if( mi.i > bestMi.i ) {
                        bestMi = mi;
                    }
                }
                displayKeywords = bestMi.name;
            }
        }
        return displayKeywords;
    }

    public int getDisplayRating() {
        if( displayRating < 0 ) {
            final List<FrostFileListFileObjectOwner> lst = getFrostFileListFileObjectOwnerList();
            if( lst == null || lst.size() == 0 ) {
                return 0;
            }
            // choose most often used rating
            // choose most often used name
            final int ratings[] = new int[6];
            for( final FrostFileListFileObjectOwner e : lst ) {
                final int r = e.getRating();
                if( r < 1 || r > 5 ) {
                    continue;
                }
                ratings[r]++;
            }
            int bestRating = 0;
            for(int x=1; x < ratings.length; x++ ) {
                if( ratings[x] > bestRating ) {
                    bestRating = x;
                }
            }
            displayRating = bestRating;
        }
        return displayRating;
    }

    public void addListener(final FrostDownloadItem d) {
        if( !getListeners().contains(d) ) {
            getListeners().add(d);
        }
    }
    public void removeListener(final FrostDownloadItem d) {
        if( getListeners().contains(d) ) {
            getListeners().remove(d);
        }
    }
    public List<FrostDownloadItem> getListeners() {
        if( listeners == null ) {
            listeners = new ArrayList<FrostDownloadItem>();
        }
        return listeners;
    }
    private void notifyListeners() {
        for( final FrostDownloadItem dl : getListeners() ) {
            dl.fireValueChanged();
        }
    }
}

src/main/java/frost/fileTransfer/FrostFileListFileObjectOwner.java
public class FrostFileListFileObjectOwner extends Persistent {

    protected FrostFileListFileObject fileListFileObject;

    protected String name;
    protected String owner;

    protected String comment;
    protected String keywords;
    protected int rating;

    protected String key;

    protected long lastReceived = 0;
    protected long lastUploaded = 0;

    public FrostFileListFileObjectOwner(
            final String newName,
            final String newOwner,
            final String newComment,
            final String newKeywords,
            final int newRating,
            final long newLastReceived,
            final long newLastUploaded,
            final String newKey)
    {
        name = newName;
        owner = newOwner;
        comment = newComment;
        keywords = newKeywords;
        rating = newRating;
        lastReceived = newLastReceived;
        lastUploaded = newLastUploaded;
        key = newKey;
    }

    public long getLastReceived() {
        return lastReceived;
    }
    public String getName() {
        return name;
    }
    public String getOwner() {
        return owner;
    }

    public long getLastUploaded() {
        return lastUploaded;
    }

    public void setLastReceived(final long lastReceived) {
        this.lastReceived = lastReceived;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public void setLastUploaded(final long newLastUploaded) {
        this.lastUploaded = newLastUploaded;
    }

    public void setOwner(final String owner) {
        this.owner = owner;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(final String comment) {
        this.comment = comment;
    }

    public String getKeywords() {
        return keywords;
    }

    public void setKeywords(final String keywords) {
        this.keywords = keywords;
    }

    public int getRating() {
        return rating;
    }

    public void setRating(final int rating) {
        this.rating = rating;
    }

    public String getKey() {
        return key;
    }

    public void setKey(final String key) {
        this.key = key;
    }

    public FrostFileListFileObject getFileListFileObject() {
        return fileListFileObject;
    }

    public void setFileListFileObject(final FrostFileListFileObject fileListFileObject) {
        this.fileListFileObject = fileListFileObject;
    }
}

src/main/java/frost/util/TextSearchFun.java
public class TextSearchFun {

    private final static SearchStringParser searchStringParser = new SearchStringParser();

    private static final String NOT_IDENT = ">?*NOT*?<";

    private static List<String> emptyList = new LinkedList<String>();

	public enum SPLIT {
		SEARCH, NOT_SEARCH
	};

    /**
     * Searches text for occurence of any of the provided strings.
     * @param text  text to search into
     * @param notStrings  list of strings
     * @return  true if any string occurs in text, false if no string occurs in text
     */
    public static boolean containsAnyString(String text, List<String> notStrings) {
        if( notStrings != null && !notStrings.isEmpty() && text != null && text.length() > 0 ) {
            for(int x=0; x < notStrings.size(); x++) {
                String notName = notStrings.get(x);
                if( text.indexOf(notName) > -1 ) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Searches text for all occurences of provided strings.
     * @param text  text to search into
     * @param strings  List of strings to search
     * @return true if ALL strings occur in the text, false otherwise
     */
    public static boolean containsEachString(String text, List<String> strings) {
        for(int x=0; x < strings.size(); x++) {
            String string = strings.get(x);
            if( text.indexOf(string) < 0 ) {
                return false;
            }
        }
        return true;
    }

    /**
     * Splits an input search string into search words and NOT search words.
     * @return  List[2] where List[0] is a list of search string and List[1] is a List of NOT search strings
     */
	public static EnumMap<SPLIT, ArrayList<String>> splitStrings(String input, boolean makeLowerCase) {

        List<String> strList;

        // we share one instance of the parser
        synchronized(searchStringParser) {
            strList = searchStringParser.parseSearchText(input);
        }

		EnumMap<SPLIT, ArrayList<String>> retVal = new EnumMap<>(SPLIT.class);
		ArrayList<String> searchStrings = new ArrayList<>();
		ArrayList<String> notSearchStrings = new ArrayList<>();
		retVal.put(SPLIT.SEARCH, searchStrings);
		retVal.put(SPLIT.NOT_SEARCH, notSearchStrings);

        // all strings until SearchStringParser.NOT_IDENT are ANDed, all after NOT are the notStrings
        boolean collectNotStrings = false;
        for(Iterator<String> i=strList.iterator(); i.hasNext(); ) {
            String s = i.next();
            if( s.equals(NOT_IDENT) ) {
                collectNotStrings = true;
            } else {
                if( makeLowerCase ) {
                    s = s.toLowerCase();
                }
                if( !collectNotStrings ) {
                    searchStrings.add(s);
                } else {
                    notSearchStrings.add(s);
                }
            }
        }
        return retVal;
    }

    /**
     * Splits a String into parts.
     * First NOT is converted to ">?*NOT*?<", more NOTs are dropped. "NOT" keeps NOT.
     * Input: "mars venus \"milky way\" NOT \"NOT\" NOT sun"
     * Output: [mars, venus, milky way, >?*NOT*?<, NOT, sun]
     */
    private static class SearchStringParser {

        /**
         * Parse the user's search box input into a Set of String tokens.
         *
         * @return Set of Strings, one for each word in fSearchText; here "word" is defined as either a lone word
         *         surrounded by whitespace, or as a series of words surrounded by double quotes, "like this".
         */
        public List<String> parseSearchText(String aSearchText) {

            if( aSearchText == null ) {
                return emptyList;
            }
            fSearchText = aSearchText;
            notAdded = false;

            List<String> result = new LinkedList<String>();

            boolean returnTokens = true;
            String currentDelims = fWHITESPACE_AND_QUOTES;
            StringTokenizer parser = new StringTokenizer(fSearchText, currentDelims, returnTokens);

            String token = null;
            boolean inQuotes = false;
            while( parser.hasMoreTokens() ) {
                token = parser.nextToken(currentDelims);
                if( !isDoubleQuote(token) ) {
                    addNonTrivialWordToResult(token, result, inQuotes);
                } else {
                    currentDelims = flipDelimiters(currentDelims);
                    inQuotes = !inQuotes;
                }
            }
            return result;
        }

        // PRIVATE //
        private String fSearchText;
        private static final String fDOUBLE_QUOTE = "\"";

        // the parser flips between these two sets of delimiters
        private static final String fWHITESPACE_AND_QUOTES = " \t\r\n\"";
        private static final String fQUOTES_ONLY = "\"";

        private boolean notAdded;

        private boolean textHasContent(String aText) {
            return (aText != null) && (aText.trim().length() > 0);
        }

        private void addNonTrivialWordToResult(String aToken, List<String> aResult, boolean inQuotes) {
            if( textHasContent(aToken) ) {
                aToken = aToken.trim();
                if( !inQuotes && aToken.equals("NOT") ) {
                    if( !notAdded ) {
                        // add NOT one time
                        aResult.add(NOT_IDENT);
                        notAdded = true;
                    }
                } else {
                    aResult.add(aToken.trim());
                }
            }
        }

        private boolean isDoubleQuote(String aToken) {
            return aToken.equals(fDOUBLE_QUOTE);
        }

        private String flipDelimiters(String aCurrentDelims) {
            String result = null;
            if( aCurrentDelims.equals(fWHITESPACE_AND_QUOTES) ) {
                result = fQUOTES_ONLY;
            } else {
                result = fWHITESPACE_AND_QUOTES;
            }
            return result;
        }
    }
}

src/main/java/frost/storage/FileListCallback.java
public interface FileListCallback {
    public boolean fileRetrieved(FrostFileListFileObject fo);
}

src/main/java/frost/storage/perst/filelist/FileListStorage.java
public class FileListStorage extends AbstractFrostStorage implements ExitSavable, PropertyChangeListener {

	private static final Logger logger =  LoggerFactory.getLogger(FileListStorage.class);

    private FileListStorageRoot storageRoot = null;

    private static final String STORAGE_FILENAME = "filelist.dbs";

    private static FileListStorage instance = new FileListStorage();

    private boolean rememberSharedFileDownloaded;

    protected FileListStorage() {
        super();
    }

    public static FileListStorage inst() {
        return instance;
    }

    @Override
    public String getStorageFilename() {
        return STORAGE_FILENAME;
    }

    @Override
    public boolean initStorage() {
        rememberSharedFileDownloaded = Core.frostSettings.getBoolValue(SettingsClass.REMEMBER_SHAREDFILE_DOWNLOADED);
        Core.frostSettings.addPropertyChangeListener(SettingsClass.REMEMBER_SHAREDFILE_DOWNLOADED, this);

        final String databaseFilePath = buildStoragePath(getStorageFilename()); // path to the database file
        final long pagePoolSize = getPagePoolSize(SettingsClass.PERST_PAGEPOOLSIZE_FILELIST);

        open(databaseFilePath, pagePoolSize, true, true, false);

        storageRoot = (FileListStorageRoot)getStorage().getRoot();
        if (storageRoot == null) {
            // Storage was not initialized yet
            storageRoot = new FileListStorageRoot(getStorage());
            getStorage().setRoot(storageRoot);
            commit(); // commit transaction
        }
        storageRoot.createNewFields(getStorage());
        return true;
    }

    public void silentClose() {
        close();
        storageRoot = null;
    }

    public void exitSave() {
        close();
        storageRoot = null;
        logger.info("FileListStorage closed.");
    }

    public IPersistentList<FrostFileListFileObjectOwner> createList() {
        return getStorage().createScalableList();
    }

    public boolean insertOrUpdateFileListFileObject(final FrostFileListFileObject flf) {
        // check for dups and update them!
        final FrostFileListFileObject pflf = storageRoot.getFileListFileObjects().get(flf.getSha());
        if( pflf == null ) {
            // insert new
            storageRoot.getFileListFileObjects().put(flf.getSha(), flf);

            for( final Iterator<FrostFileListFileObjectOwner> i = flf.getFrostFileListFileObjectOwnerIterator(); i.hasNext(); ) {
                final FrostFileListFileObjectOwner o = i.next();
                addFileListFileOwnerToIndices(o);
            }
        } else {
            // update existing
            updateFileListFileFromOtherFileListFile(pflf, flf);
        }
        return true;
    }

    /**
     * Adds a new FrostFileListFileObjectOwner to the indices.
     */
    private void addFileListFileOwnerToIndices(final FrostFileListFileObjectOwner o) {
        // maybe the owner already shares other files
        PerstIdentitiesFiles pif = storageRoot.getIdentitiesFiles().get(o.getOwner());
        if( pif == null ) {
            pif = new PerstIdentitiesFiles(o.getOwner(), getStorage());
            storageRoot.getIdentitiesFiles().put(o.getOwner(), pif);
        }
        pif.addFileToIdentity(o);

        // add to indices
        maybeAddFileListFileInfoToIndex(o.getName(), o, storageRoot.getFileNameIndex());
        maybeAddFileListFileInfoToIndex(o.getComment(), o, storageRoot.getFileCommentIndex());
        maybeAddFileListFileInfoToIndex(o.getKeywords(), o, storageRoot.getFileKeywordIndex());
        maybeAddFileListFileInfoToIndex(o.getOwner(), o, storageRoot.getFileOwnerIndex());
    }

    private void maybeAddFileListFileInfoToIndex(String lName, final FrostFileListFileObjectOwner o, final Index<PerstFileListIndexEntry> ix) {
        if( lName == null || lName.length() == 0 ) {
            return;
        }
        lName = lName.toLowerCase();
        PerstFileListIndexEntry ie = ix.get(lName);
        if( ie == null ) {
            ie = new PerstFileListIndexEntry(getStorage());
            ix.put(lName, ie);
        }
        ie.getFileOwnersWithText().add(o);
    }

    public FrostFileListFileObject getFileBySha(final String sha) {
        if( !beginCooperativeThreadTransaction() ) {
            return null;
        }
        final FrostFileListFileObject o;
        try {
            o = storageRoot.getFileListFileObjects().get(sha);
        } finally {
            endThreadTransaction();
        }
        return o;
    }

    public int getFileCount() {
        if( !beginCooperativeThreadTransaction() ) {
            return 0;
        }
        final int count;
        try {
            count = storageRoot.getFileListFileObjects().size();
        } finally {
            endThreadTransaction();
        }
        return count;
    }

    public int getFileCount(final String idUniqueName) {
        if( !beginCooperativeThreadTransaction() ) {
            return 0;
        }
        final int count;
        try {
            final PerstIdentitiesFiles pif = storageRoot.getIdentitiesFiles().get(idUniqueName);
            if( pif != null ) {
                count = pif.getFilesFromIdentity().size();
            } else {
                count = 0;
            }
        } finally {
            endThreadTransaction();
        }
        return count;
    }

    public int getSharerCount() {
        if( !beginCooperativeThreadTransaction() ) {
            return 0;
        }
        final int count;
        try {
            count = storageRoot.getIdentitiesFiles().size();
        } finally {
            endThreadTransaction();
        }
        return count;
    }

    // FIXME: implement this faster, e.g. maintain the sum in storageRoot, update on each insert/remove
    public long getFileSizes() {
        if( !beginCooperativeThreadTransaction() ) {
            return 0L;
        }
        long sizes = 0;
        try {
            for( final FrostFileListFileObject fo : storageRoot.getFileListFileObjects() ) {
                sizes += fo.getSize();
            }
        } finally {
            endThreadTransaction();
        }
        return sizes;
    }

    /**
     * Must be called within a perst thread transaction!
     */
    public void markFileListFileHidden(final FrostFileListFileObject fof) {
        if (!fof.isHidden()) {
            fof.setHidden(true);
            fof.modify();
            storageRoot.getHiddenFileOids().add(new PerstHiddenFileOid(fof.getOid()));
        }
    }

    public void resetHiddenFiles() {
        if( beginExclusiveThreadTransaction() ) {
            try {
            	final Storage storage = getStorage();
                for (final Iterator<PerstHiddenFileOid> it = storageRoot.getHiddenFileOids().iterator(); it.hasNext(); ) {
                    final PerstHiddenFileOid hf = it.next();
                    final FrostFileListFileObject fof = (FrostFileListFileObject) storage.getObjectByOID(hf.getHiddenFileOid());
                    if (fof != null && fof.isHidden()) {
                        fof.setHidden(false);
                        fof.modify();
                    }
                    it.remove();
                    hf.deallocate();
                }
            } finally {
                endThreadTransaction();
            }
        }
    }

    public int getHiddenFilesCount() {
        return storageRoot.getHiddenFileOids().size();
    }

    private void maybeRemoveFileListFileInfoFromIndex(
            final String lName,
            final FrostFileListFileObjectOwner o,
            final Index<PerstFileListIndexEntry> ix)
    {
        if( lName != null && lName.length() > 0 ) {
            final String lowerCaseName = lName.toLowerCase();
            final PerstFileListIndexEntry ie = ix.get(lowerCaseName);
            if( ie != null ) {
                ie.getFileOwnersWithText().remove(o);

                if( ie.getFileOwnersWithText().size() == 0 ) {
                    // no more owners for this text, remove from index
                    if( ix.remove(lowerCaseName) != null ) {
                        ie.deallocate();
                    }
                }
            }
        }
    }

    /**
     * Remove owners that were not seen for more than MINIMUM_DAYS_OLD days and have no CHK key set.
     */
    public int cleanupFileListFileOwners(final boolean removeOfflineFilesWithKey, final int offlineFilesMaxDaysOld) {

        if( !beginExclusiveThreadTransaction() ) {
            return 0;
        }

        int count = 0;
        try {
            final long minVal = System.currentTimeMillis() - (offlineFilesMaxDaysOld * 24L * 60L * 60L * 1000L);
            for( final PerstIdentitiesFiles pif : storageRoot.getIdentitiesFiles() ) {
                for( final Iterator<FrostFileListFileObjectOwner> i = pif.getFilesFromIdentity().iterator(); i.hasNext(); ) {
                    final FrostFileListFileObjectOwner o = i.next();
                    boolean remove = false;

                    if( o.getLastReceived() < minVal ) {
                        // outdated owner
                        if( o.getKey() != null && o.getKey().length() > 0 ) {
                            // has a key
                            if( removeOfflineFilesWithKey ) {
                                remove = true;
                            }
                        } else {
                            // has no key
                            remove = true;
                        }
                    }

                    if( remove ) {
                        // remove this owner file info from file list object
                        final FrostFileListFileObject fof = o.getFileListFileObject();
                        o.setFileListFileObject(null);
                        fof.deleteFrostFileListFileObjectOwner(o);

                        // remove from indices
                        maybeRemoveFileListFileInfoFromIndex(o.getName(), o, storageRoot.getFileNameIndex());
                        maybeRemoveFileListFileInfoFromIndex(o.getComment(), o, storageRoot.getFileCommentIndex());
                        maybeRemoveFileListFileInfoFromIndex(o.getKeywords(), o, storageRoot.getFileKeywordIndex());
                        maybeRemoveFileListFileInfoFromIndex(o.getOwner(), o, storageRoot.getFileOwnerIndex());

                        // remove this owner file info from identities files
                        i.remove();
                        // delete from store
                        o.deallocate();

                        fof.modify();

                        count++;
                    }
                }
                if( pif.getFilesFromIdentity().size() == 0 ) {
                    // no more files for this identity, remove
                    if( storageRoot.getIdentitiesFiles().remove(pif.getUniqueName()) != null ) {
                        pif.deallocate();
                    }
                }
            }
        } finally {
            endThreadTransaction();
        }
        return count;
    }

    /**
     * Remove files that have no owner.
     */
    public int cleanupFileListFiles() {
        if( !beginExclusiveThreadTransaction() ) {
            return 0;
        }

        int count = 0;
        try {
            final HashSet<Integer> oidsToRemove = new HashSet<Integer>();
            for( final Iterator<FrostFileListFileObject> i = storageRoot.getFileListFileObjects().iterator(); i.hasNext(); ) {
                final FrostFileListFileObject fof = i.next();
                if( fof.getFrostFileListFileObjectOwnerListSize() == 0 ) {
                    // no more owners, we also have no name, remove
					oidsToRemove.add(fof.getOid());
                    i.remove();
                    fof.deallocate();
                    count++;
                }
            }
            // remove deleted files from hiddenFilesOid list
            for (final Iterator<PerstHiddenFileOid> it = storageRoot.getHiddenFileOids().iterator(); it.hasNext(); ) {
                final PerstHiddenFileOid hf = it.next();
				if (oidsToRemove.contains(hf.getHiddenFileOid())) {
                    it.remove();
                    hf.deallocate();
                }
            }
        } finally {
            endThreadTransaction();
        }

        return count;
    }

    /**
     * Reset the lastdownloaded column for all file entries.
     */
    public void resetLastDownloaded() {

        if( !beginExclusiveThreadTransaction() ) {
            return;
        }
        try {
            for( final FrostFileListFileObject fof : storageRoot.getFileListFileObjects() ) {
                fof.setLastDownloaded(0);
                fof.modify();
            }
        } finally {
            endThreadTransaction();
        }
    }

    /**
     * Update the item with SHA, set requestlastsent and requestssentcount.
     * Does NOT commit!
     */
    public boolean updateFrostFileListFileObjectAfterRequestSent(final String sha, final long requestLastSent) {

        final FrostFileListFileObject oldSfo = getFileBySha(sha);
        if( oldSfo == null ) {
            return false;
        }

        oldSfo.setRequestLastSent(requestLastSent);
        oldSfo.setRequestsSentCount(oldSfo.getRequestsSentCount() + 1);

        oldSfo.modify();

        return true;
    }

    /**
     * Update the item with SHA, set requestlastsent and requestssentcount
     * Does NOT commit!
     */
    public boolean updateFrostFileListFileObjectAfterRequestReceived(final String sha, long requestLastReceived) {

        final FrostFileListFileObject oldSfo = getFileBySha(sha);
        if( oldSfo == null ) {
            return false;
        }

        if( oldSfo.getRequestLastReceived() > requestLastReceived ) {
            requestLastReceived = oldSfo.getRequestLastReceived();
        }

        oldSfo.setRequestLastReceived(requestLastReceived);
        oldSfo.setRequestsReceivedCount(oldSfo.getRequestsReceivedCount() + 1);

        oldSfo.modify();

        return true;
    }

    /**
     * Update the item with SHA, set lastdownloaded
     */
    public boolean updateFrostFileListFileObjectAfterDownload(final String sha, final long lastDownloaded) {

        if( !rememberSharedFileDownloaded ) {
            return true;
        }

        if( !beginExclusiveThreadTransaction() ) {
            return false;
        }

        try {
            final FrostFileListFileObject oldSfo = getFileBySha(sha);
            if( oldSfo == null ) {
                endThreadTransaction();
                return false;
            }
            oldSfo.setLastDownloaded(lastDownloaded);
            oldSfo.modify();
        } finally {
            endThreadTransaction();
        }

        return true;
    }

    /**
     * Retrieves a list of FrostSharedFileOjects.
     */
    public void retrieveFiles(
            final FileListCallback callback,
            final List<String> names,
            final List<String> comments,
            final List<String> keywords,
            final List<String> owners,
            String[] extensions)
    {
        if( !beginCooperativeThreadTransaction() ) {
            return;
        }

        logger.info("Starting file search...");
        final long t = System.currentTimeMillis();

        boolean searchForNames = true;
        boolean searchForComments = true;
        boolean searchForKeywords = true;
        boolean searchForOwners = true;
        boolean searchForExtensions = true;

        if( names == null    || names.size() == 0 )    { searchForNames = false; }
        if( comments == null || comments.size() == 0 ) { searchForComments = false; }
        if( keywords == null || keywords.size() == 0 ) { searchForKeywords = false; }
        if( owners == null   || owners.size() == 0 )   { searchForOwners = false; }
        if( extensions == null || extensions.length == 0 )   { searchForExtensions = false; }

        try {
            if( !searchForNames && !searchForComments && ! searchForKeywords && !searchForOwners && !searchForExtensions) {
                // find ALL files
                for(final FrostFileListFileObject o : storageRoot.getFileListFileObjects()) {
                    if(callback.fileRetrieved(o)) {
                        return; // stop requested
                    }
                }
                return;
            }

            if( !searchForExtensions ) {
                extensions = null;
            }

            final HashSet<Integer> ownerOids = new HashSet<Integer>();

            if( searchForNames || searchForExtensions ) {
                searchForFiles(ownerOids, names, extensions, storageRoot.getFileNameIndex());
            }

            if( searchForComments ) {
                searchForFiles(ownerOids, comments, null, storageRoot.getFileCommentIndex());
            }

            if( searchForKeywords ) {
                searchForFiles(ownerOids, keywords, null, storageRoot.getFileKeywordIndex());
            }

            if( searchForOwners ) {
                searchForFiles(ownerOids, owners, null, storageRoot.getFileOwnerIndex());
            }

            // collect oids to prevent duplicate FileListFileObjects
            final HashSet<Integer> foundFileObjectOids = new HashSet<Integer>();

            for( final Integer i : ownerOids ) {
                final FrostFileListFileObjectOwner owner = (FrostFileListFileObjectOwner) getStorage().getObjectByOID(i);
                final FrostFileListFileObject fileObject = owner.getFileListFileObject();
                if (fileObject != null) {
                    if (fileObject.isHidden()) {
                        // ignore hidden file
                        continue;
                    }
                    final int oid = fileObject.getOid();
                    if (!foundFileObjectOids.contains(oid)) {
                        foundFileObjectOids.add(oid);
                        if (callback.fileRetrieved(fileObject)) {
                            return;
                        }
                    }
                }
            }
        } finally {
            logger.info("Finished file search, duration = {}", System.currentTimeMillis() - t);
            endThreadTransaction();
        }
    }

    private void searchForFiles(
            final HashSet<Integer> oids,
            final List<String> searchStrings,
            final String[] extensions, // only used for name search
            final Index<PerstFileListIndexEntry> ix)
    {
        for(final Map.Entry<Object,PerstFileListIndexEntry> entry : ix.entryIterator() ) {
            final String key = (String)entry.getKey();
            IPersistentList<FrostFileListFileObjectOwner> fileOwnersWithText = entry.getValue().getFileOwnersWithText();

            if( searchStrings != null ) {
                for(final String searchString : searchStrings) {
                    if( key.indexOf(searchString) > -1 ) {
                        // add all owner oids
                    	final Iterator<FrostFileListFileObjectOwner> i = fileOwnersWithText.iterator();
                        while(i.hasNext()) {
                            final int oid = ((PersistentIterator)i).nextOid();
                            oids.add(oid);
                        }
                    }
                }
            }

            if( extensions != null ) {
                for( final String extension : extensions ) {
                    if( key.endsWith(extension) ) {
                        // add all owner oids
                        final Iterator<FrostFileListFileObjectOwner> i = fileOwnersWithText.iterator();
                        while(i.hasNext()) {
                            final int oid = ((PersistentIterator)i).nextOid();
                            oids.add(oid);
                        }
                    }
                }
            }
        }
    }

boolean alwaysUseLatestChkKey = true; // FIXME: must be true as long as the key changes now and then. False prevents fake files.

    private boolean updateFileListFileFromOtherFileListFile(final FrostFileListFileObject oldFof, final FrostFileListFileObject newFof) {
        // file is already in FILELIST table, maybe add new FILEOWNER and update fields
        // maybe update oldSfo
        boolean doUpdate = false;
        if( oldFof.getKey() == null && newFof.getKey() != null ) {
            oldFof.setKey(newFof.getKey()); doUpdate = true;
        } else if( alwaysUseLatestChkKey
                && oldFof.getLastUploaded() < newFof.getLastUploaded()
                && oldFof.getKey() != null
                && newFof.getKey() != null
                && !oldFof.getKey().equals(newFof.getKey()) )
        {
            oldFof.setKey(newFof.getKey()); doUpdate = true;
        }
        if( oldFof.getFirstReceived() > newFof.getFirstReceived() ) {
            oldFof.setFirstReceived(newFof.getFirstReceived()); doUpdate = true;
        }
        if( oldFof.getLastReceived() < newFof.getLastReceived() ) {
            oldFof.setLastReceived(newFof.getLastReceived()); doUpdate = true;
        }
        if( oldFof.getLastUploaded() < newFof.getLastUploaded() ) {
            oldFof.setLastUploaded(newFof.getLastUploaded()); doUpdate = true;
        }
        if( oldFof.getLastDownloaded() < newFof.getLastDownloaded() ) {
            oldFof.setLastDownloaded(newFof.getLastDownloaded()); doUpdate = true;
        }
        if( oldFof.getRequestLastReceived() < newFof.getRequestLastReceived() ) {
            oldFof.setRequestLastReceived(newFof.getRequestLastReceived()); doUpdate = true;
        }
        if( oldFof.getRequestLastSent() < newFof.getRequestLastSent() ) {
            oldFof.setRequestLastSent(newFof.getRequestLastSent()); doUpdate = true;
        }
        if( oldFof.getRequestsReceivedCount() < newFof.getRequestsReceivedCount() ) {
            oldFof.setRequestsReceivedCount(newFof.getRequestsReceivedCount()); doUpdate = true;
        }
        if( oldFof.getRequestsSentCount() < newFof.getRequestsSentCount() ) {
            oldFof.setRequestsSentCount(newFof.getRequestsSentCount()); doUpdate = true;
        }

        for(final Iterator<FrostFileListFileObjectOwner> i = newFof.getFrostFileListFileObjectOwnerIterator(); i.hasNext(); ) {

            final FrostFileListFileObjectOwner obNew = i.next();

            // check if we have an owner object for this sharer
            FrostFileListFileObjectOwner obOld = null;
            for(final Iterator<FrostFileListFileObjectOwner> j = oldFof.getFrostFileListFileObjectOwnerIterator(); j.hasNext(); ) {
                final FrostFileListFileObjectOwner o = j.next();
                if( o.getOwner().equals(obNew.getOwner()) ) {
                    obOld = o;
                    break;
                }
            }

            if( obOld == null ) {
                // add new
                oldFof.addFrostFileListFileObjectOwner(obNew);
                addFileListFileOwnerToIndices(obNew);
                doUpdate = true;
            } else {
                // update existing
                if( obOld.getLastReceived() < obNew.getLastReceived() ) {

                    maybeUpdateFileListInfoInIndex(obOld.getName(), obNew.getName(), obOld, storageRoot.getFileNameIndex());
                    obOld.setName(obNew.getName());

                    maybeUpdateFileListInfoInIndex(obOld.getComment(), obNew.getComment(), obOld, storageRoot.getFileCommentIndex());
                    obOld.setComment(obNew.getComment());

                    maybeUpdateFileListInfoInIndex(obOld.getKeywords(), obNew.getKeywords(), obOld, storageRoot.getFileKeywordIndex());
                    obOld.setKeywords(obNew.getKeywords());

                    obOld.setLastReceived(obNew.getLastReceived());
                    obOld.setLastUploaded(obNew.getLastUploaded());
                    obOld.setRating(obNew.getRating());
                    obOld.setKey(obNew.getKey());

                    obOld.modify();

                    doUpdate = true;
                }
            }
        }

        if( doUpdate ) {
            oldFof.modify();
        }

        return doUpdate;
    }

    private void maybeUpdateFileListInfoInIndex(
            final String oldValue,
            final String newValue,
            final FrostFileListFileObjectOwner o,
            final Index<PerstFileListIndexEntry> ix)
    {
        // remove current value from index of needed, add new value to index if needed
        if( oldValue != null ) {
            if( newValue != null ) {
                if( oldValue.toLowerCase().equals(newValue.toLowerCase()) ) {
                    // value not changed, ignore index change
                    return;
                }
                // we have to add this value to the index
                maybeAddFileListFileInfoToIndex(newValue, o, ix);
            }
            // we have to remove the old value from index
            maybeRemoveFileListFileInfoFromIndex(oldValue, o, ix);
        }
    }

    public void propertyChange(final PropertyChangeEvent evt) {
        rememberSharedFileDownloaded = Core.frostSettings.getBoolValue(SettingsClass.REMEMBER_SHAREDFILE_DOWNLOADED);
    }
}

Answer: for( Iterator<FrostFileListFileObjectOwner> i = fo.getFrostFileListFileObjectOwnerIterator(); i.hasNext(); ) {

<think>
The method searchFile1 begins checking hide‑file options. After initializing a boolean accept = true, the code will need to examine each owner of the file to possibly set accept to false based on hideBadUserFiles / hideCheckUserFiles / hideObserveUserFiles flags. In the Frost codebase owners are iterated with an Iterator obtained via fo.getFrostFileListFileObjectOwnerIterator(). Therefore the next line should start that loop.
</think>
for (final Iterator<FrostFileListFileObjectOwner> i = fo.getFrostFileListFileObjectOwnerIterator(); i.hasNext(); ) {