Introducing an API for local caching of remote results.
authorJan Lahoda <jlahoda@netbeans.org>
Wed, 08 Aug 2012 18:39:56 +0200
changeset 84066aefaa70f32
parent 839 91c03aa50d65
child 841 d1ef140c28c4
Introducing an API for local caching of remote results.
duplicates/ide/impl/src/org/netbeans/modules/jackpot30/impl/duplicates/indexing/RemoteDuplicatesIndex.java
remoting/ide/api/nbproject/genfiles.properties
remoting/ide/api/nbproject/project.xml
remoting/ide/api/src/org/netbeans/modules/jackpot30/remoting/api/LocalCache.java
remoting/ide/api/src/org/netbeans/modules/jackpot30/remotingapi/CacheFolder.java
     1.1 --- a/duplicates/ide/impl/src/org/netbeans/modules/jackpot30/impl/duplicates/indexing/RemoteDuplicatesIndex.java	Tue Jul 31 21:05:52 2012 +0200
     1.2 +++ b/duplicates/ide/impl/src/org/netbeans/modules/jackpot30/impl/duplicates/indexing/RemoteDuplicatesIndex.java	Wed Aug 08 18:39:56 2012 +0200
     1.3 @@ -42,20 +42,15 @@
     1.4  
     1.5  package org.netbeans.modules.jackpot30.impl.duplicates.indexing;
     1.6  
     1.7 -import java.io.File;
     1.8 -import java.io.FileOutputStream;
     1.9  import java.io.IOException;
    1.10 -import java.io.OutputStream;
    1.11  import java.net.URI;
    1.12  import java.net.URISyntaxException;
    1.13 -import java.util.AbstractMap.SimpleEntry;
    1.14  import java.util.ArrayList;
    1.15  import java.util.Arrays;
    1.16  import java.util.BitSet;
    1.17  import java.util.Collection;
    1.18  import java.util.Collections;
    1.19  import java.util.Comparator;
    1.20 -import java.util.HashMap;
    1.21  import java.util.HashSet;
    1.22  import java.util.Iterator;
    1.23  import java.util.LinkedHashMap;
    1.24 @@ -75,24 +70,21 @@
    1.25  import org.apache.lucene.document.Field.Store;
    1.26  import org.apache.lucene.index.IndexReader;
    1.27  import org.apache.lucene.index.IndexWriter;
    1.28 -import org.apache.lucene.index.IndexWriter.MaxFieldLength;
    1.29  import org.apache.lucene.index.Term;
    1.30  import org.apache.lucene.search.Collector;
    1.31  import org.apache.lucene.search.IndexSearcher;
    1.32  import org.apache.lucene.search.Query;
    1.33  import org.apache.lucene.search.Searcher;
    1.34  import org.apache.lucene.search.TermQuery;
    1.35 -import org.apache.lucene.store.FSDirectory;
    1.36  import org.codeviation.pojson.Pojson;
    1.37 -import org.netbeans.modules.jackpot30.common.api.IndexAccess.NoAnalyzer;
    1.38  import org.netbeans.modules.jackpot30.common.api.LuceneHelpers.BitSetCollector;
    1.39  import org.netbeans.modules.jackpot30.impl.duplicates.ComputeDuplicates.DuplicateDescription;
    1.40  import org.netbeans.modules.jackpot30.impl.duplicates.ComputeDuplicates.Span;
    1.41 +import org.netbeans.modules.jackpot30.remoting.api.LocalCache;
    1.42 +import org.netbeans.modules.jackpot30.remoting.api.LocalCache.Task;
    1.43  import org.netbeans.modules.jackpot30.remoting.api.RemoteIndex;
    1.44  import org.netbeans.modules.jackpot30.remoting.api.WebUtilities;
    1.45 -import org.netbeans.modules.parsing.impl.indexing.CacheFolder;
    1.46  import org.openide.filesystems.FileObject;
    1.47 -import org.openide.filesystems.FileUtil;
    1.48  import org.openide.filesystems.URLMapper;
    1.49  import org.openide.util.Exceptions;
    1.50  
    1.51 @@ -103,7 +95,6 @@
    1.52  @SuppressWarnings("ClassWithMultipleLoggers")
    1.53  public class RemoteDuplicatesIndex {
    1.54  
    1.55 -    private static final Logger LOG = Logger.getLogger(RemoteDuplicatesIndex.class.getName());
    1.56      private static final Logger TIMER = Logger.getLogger("TIMER");
    1.57  
    1.58      public static List<DuplicateDescription> findDuplicates(Map<String, long[]> hashes, FileObject currentFile, AtomicBoolean cancel) throws IOException, URISyntaxException {
    1.59 @@ -209,123 +200,60 @@
    1.60          }
    1.61      }
    1.62  
    1.63 -    private static final Map<URI, IndexReader> readerCache = new HashMap<URI, IndexReader>();
    1.64 +    private static Map<String, Map<String, Collection<? extends String>>> findHashOccurrencesInLocalCache(RemoteIndex ri, final Iterable<? extends String> hashes, AtomicBoolean cancel) throws IOException, URISyntaxException {
    1.65 +        return LocalCache.runOverLocalCache(ri, new Task<IndexReader, Map<String, Map<String, Collection<? extends String>>>>() {
    1.66 +            @Override public Map<String, Map<String, Collection<? extends String>>> run(IndexReader reader, AtomicBoolean cancel) throws IOException {
    1.67 +                Map<String, Map<String, Collection<String>>> result = new LinkedHashMap<String, Map<String, Collection<String>>>();
    1.68  
    1.69 -    private static File findLocalCacheDir(RemoteIndex ri) throws IOException {
    1.70 -        return new File(FileUtil.toFile(FileUtil.createFolder(CacheFolder.getDataFolder(ri.remote), "remote-duplicates")), ri.remoteSegment);
    1.71 +                for (Entry<String, Collection<? extends String>> e : containsHash(reader, hashes, cancel).entrySet()) {
    1.72 +                    if (cancel.get()) return Collections.emptyMap();
    1.73 +
    1.74 +                    Map<String, Collection<String>> forHash = result.get(e.getKey());
    1.75 +
    1.76 +                    if (forHash == null) {
    1.77 +                        result.put(e.getKey(), forHash = new LinkedHashMap<String, Collection<String>>());
    1.78 +                    }
    1.79 +
    1.80 +                    for (String path : e.getValue()) {
    1.81 +                        String segment = path.substring(0, path.indexOf('/'));
    1.82 +
    1.83 +                        path = path.substring(path.indexOf('/') + 1);
    1.84 +
    1.85 +                        Collection<String> list = forHash.get(segment);
    1.86 +
    1.87 +                        if (list == null) {
    1.88 +                            forHash.put(segment, list = new LinkedList<String>());
    1.89 +                        }
    1.90 +
    1.91 +                        list.add(path);
    1.92 +                    }
    1.93 +                }
    1.94 +
    1.95 +                return (Map)result; //XXX
    1.96 +            }
    1.97 +        }, Collections.<String, Map<String, Collection<? extends String>>>emptyMap(), cancel);
    1.98      }
    1.99  
   1.100 -    private static final long VERSION_CHECK_PERIOD = 60 * 60 * 1000;
   1.101 -    private static final Map<Entry<URI, String>, Long> lastVersionCheck = new HashMap<Entry<URI, String>, Long>();
   1.102 -    
   1.103 -    private static synchronized Map<String, Map<String, Collection<? extends String>>> findHashOccurrencesInLocalCache(RemoteIndex ri, Iterable<? extends String> hashes, AtomicBoolean cancel) throws IOException, URISyntaxException {
   1.104 -        URI uri = ri.remote.toURI();
   1.105 -        SimpleEntry<URI, String> versionCheckKey = new SimpleEntry<URI, String>(uri, ri.remoteSegment);
   1.106 -        Long lastCheck = lastVersionCheck.get(versionCheckKey);
   1.107 +    private static synchronized void saveToLocalCache(RemoteIndex ri, final Map<String, Map<String, Collection<? extends String>>> what) throws IOException, URISyntaxException {
   1.108 +        LocalCache.saveToLocalCache(ri, new Task<IndexWriter, Void>() {
   1.109 +            @Override public Void run(IndexWriter w, AtomicBoolean cancel) throws IOException {
   1.110 +                for (Entry<String, Map<String, Collection<? extends String>>> e : what.entrySet()) {
   1.111 +                    Document doc = new Document();
   1.112  
   1.113 -        if (lastCheck == null || (System.currentTimeMillis() - lastCheck) > VERSION_CHECK_PERIOD) {
   1.114 -            File dir = findLocalCacheDir(ri);
   1.115 -            File remoteVersion = new File(dir, "remoteVersion");
   1.116 -            FileObject remoteVersionFO = FileUtil.toFileObject(remoteVersion);
   1.117 -            String previousVersion = remoteVersionFO != null ? remoteVersionFO.asText("UTF-8") : null;
   1.118 -            URI infoURI = new URI(ri.remote.toExternalForm() + "/info?path=" + WebUtilities.escapeForQuery(ri.remoteSegment));
   1.119 -            String infoContent = WebUtilities.requestStringResponse(infoURI, cancel);
   1.120 +                    doc.add(new Field("hash", e.getKey(), Store.YES, Index.NOT_ANALYZED));
   1.121  
   1.122 -            if (cancel.get()) return Collections.emptyMap();
   1.123 -            
   1.124 -            if (infoContent != null) {
   1.125 -                Object buildId = Pojson.load(LinkedHashMap.class, infoContent).get("BUILD_ID");
   1.126 -
   1.127 -                if (buildId != null && !(buildId = buildId.toString()).equals(previousVersion)) {
   1.128 -                    remoteVersion.getParentFile().mkdirs();
   1.129 -                    OutputStream out = new FileOutputStream(remoteVersion);
   1.130 -                    try {
   1.131 -                        out.write(buildId.toString().getBytes("UTF-8"));
   1.132 -                    } finally {
   1.133 -                        out.close();
   1.134 +                    for (Entry<String, Collection<? extends String>> pe : e.getValue().entrySet()) {
   1.135 +                        for (String path : pe.getValue()) {
   1.136 +                            doc.add(new Field("path", pe.getKey() + "/" + path, Store.YES, Index.NO));
   1.137 +                        }
   1.138                      }
   1.139  
   1.140 -                    LOG.log(Level.FINE, "Deleting local cache");
   1.141 -                    delete(new File(dir, "index"));
   1.142 -
   1.143 -                    IndexReader reader = readerCache.remove(uri);
   1.144 -                    if (reader != null)
   1.145 -                        reader.close();
   1.146 -                    
   1.147 -                }
   1.148 -            }
   1.149 -
   1.150 -            lastVersionCheck.put(versionCheckKey, System.currentTimeMillis());
   1.151 -        }
   1.152 -
   1.153 -        IndexReader reader = readerCache.get(uri);
   1.154 -
   1.155 -        if (reader == null && !cancel.get()) {
   1.156 -            File dir = new File(findLocalCacheDir(ri), "index");
   1.157 -
   1.158 -            if (dir.listFiles() != null && dir.listFiles().length > 0) {
   1.159 -                readerCache.put(uri, reader = IndexReader.open(FSDirectory.open(dir), true));
   1.160 -            }
   1.161 -        }
   1.162 -
   1.163 -        if (reader == null || cancel.get()) {
   1.164 -            return Collections.emptyMap();
   1.165 -        }
   1.166 -
   1.167 -        Map<String, Map<String, Collection<String>>> result = new LinkedHashMap<String, Map<String, Collection<String>>>();
   1.168 -
   1.169 -        for (Entry<String, Collection<? extends String>> e : containsHash(reader, hashes, cancel).entrySet()) {
   1.170 -            if (cancel.get()) return Collections.emptyMap();
   1.171 -
   1.172 -            Map<String, Collection<String>> forHash = result.get(e.getKey());
   1.173 -
   1.174 -            if (forHash == null) {
   1.175 -                result.put(e.getKey(), forHash = new LinkedHashMap<String, Collection<String>>());
   1.176 -            }
   1.177 -
   1.178 -            for (String path : e.getValue()) {
   1.179 -                String segment = path.substring(0, path.indexOf('/'));
   1.180 -
   1.181 -                path = path.substring(path.indexOf('/') + 1);
   1.182 -
   1.183 -                Collection<String> list = forHash.get(segment);
   1.184 -
   1.185 -                if (list == null) {
   1.186 -                    forHash.put(segment, list = new LinkedList<String>());
   1.187 +                    w.addDocument(doc);
   1.188                  }
   1.189  
   1.190 -                list.add(path);
   1.191 +                return null;
   1.192              }
   1.193 -        }
   1.194 -
   1.195 -        return (Map)result; //XXX
   1.196 -    }
   1.197 -
   1.198 -    private static synchronized void saveToLocalCache(RemoteIndex ri, Map<String, Map<String, Collection<? extends String>>> what) throws IOException, URISyntaxException {
   1.199 -        IndexReader r = readerCache.remove(ri.remote.toURI());
   1.200 -
   1.201 -        if (r != null) {
   1.202 -            r.close();
   1.203 -        }
   1.204 -        
   1.205 -        IndexWriter w = new IndexWriter(FSDirectory.open(new File(findLocalCacheDir(ri), "index")), new NoAnalyzer(), MaxFieldLength.UNLIMITED);
   1.206 -
   1.207 -        for (Entry<String, Map<String, Collection<? extends String>>> e : what.entrySet()) {
   1.208 -            Document doc = new Document();
   1.209 -
   1.210 -            doc.add(new Field("hash", e.getKey(), Store.YES, Index.NOT_ANALYZED));
   1.211 -
   1.212 -            for (Entry<String, Collection<? extends String>> pe : e.getValue().entrySet()) {
   1.213 -                for (String path : pe.getValue()) {
   1.214 -                    doc.add(new Field("path", pe.getKey() + "/" + path, Store.YES, Index.NO));
   1.215 -                }
   1.216 -            }
   1.217 -
   1.218 -            w.addDocument(doc);
   1.219 -        }
   1.220 -
   1.221 -        w.optimize();
   1.222 -        w.close();
   1.223 +        });
   1.224      }
   1.225      
   1.226      private static List<DuplicateDescription> translate(Map<String, long[]> hashes, Map<String, Map<RemoteIndex, Collection<String>>> occ, FileObject currentFile) {
   1.227 @@ -396,35 +324,6 @@
   1.228          });
   1.229      }
   1.230  
   1.231 -    private static boolean subsumes(DuplicateDescription bigger, DuplicateDescription smaller) {
   1.232 -        Set<FileObject> bFiles = new HashSet<FileObject>();
   1.233 -
   1.234 -        for (Span s : bigger.dupes) {
   1.235 -            bFiles.add(s.file);
   1.236 -        }
   1.237 -
   1.238 -        Set<FileObject> sFiles = new HashSet<FileObject>();
   1.239 -
   1.240 -        for (Span s : smaller.dupes) {
   1.241 -            sFiles.add(s.file);
   1.242 -        }
   1.243 -
   1.244 -        if (!bFiles.equals(sFiles)) return false;
   1.245 -
   1.246 -        Span testAgainst = bigger.dupes.get(0);
   1.247 -
   1.248 -        for (Span s : smaller.dupes) {
   1.249 -            if (s.file == testAgainst.file) {
   1.250 -                if (   (testAgainst.startOff <= s.startOff && testAgainst.endOff > s.endOff)
   1.251 -                    || (testAgainst.startOff < s.startOff && testAgainst.endOff >= s.endOff)) {
   1.252 -                    return true;
   1.253 -                }
   1.254 -            }
   1.255 -        }
   1.256 -
   1.257 -        return false;
   1.258 -    }
   1.259 -
   1.260      private static Map<String, Collection<? extends String>> containsHash(IndexReader reader, Iterable<? extends String> hashes, AtomicBoolean cancel) throws IOException {
   1.261          Map<String, Collection<? extends String>> result = new LinkedHashMap<String, Collection<? extends String>>();
   1.262  
   1.263 @@ -458,15 +357,4 @@
   1.264          return result;
   1.265      }
   1.266  
   1.267 -    private static void delete(File file) {
   1.268 -        File[] c = file.listFiles();
   1.269 -
   1.270 -        if (c != null) {
   1.271 -            for (File cc : c) {
   1.272 -                delete(cc);
   1.273 -            }
   1.274 -        }
   1.275 -
   1.276 -        file.delete();
   1.277 -    }
   1.278  }
     2.1 --- a/remoting/ide/api/nbproject/genfiles.properties	Tue Jul 31 21:05:52 2012 +0200
     2.2 +++ b/remoting/ide/api/nbproject/genfiles.properties	Wed Aug 08 18:39:56 2012 +0200
     2.3 @@ -3,6 +3,6 @@
     2.4  build.xml.stylesheet.CRC32=a56c6a5b@1.47
     2.5  # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
     2.6  # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
     2.7 -nbproject/build-impl.xml.data.CRC32=e364f985
     2.8 +nbproject/build-impl.xml.data.CRC32=f9a67ab1
     2.9  nbproject/build-impl.xml.script.CRC32=33cac223
    2.10 -nbproject/build-impl.xml.stylesheet.CRC32=238281d1@2.49
    2.11 +nbproject/build-impl.xml.stylesheet.CRC32=238281d1@2.53
     3.1 --- a/remoting/ide/api/nbproject/project.xml	Tue Jul 31 21:05:52 2012 +0200
     3.2 +++ b/remoting/ide/api/nbproject/project.xml	Wed Aug 08 18:39:56 2012 +0200
     3.3 @@ -25,6 +25,23 @@
     3.4                      </run-dependency>
     3.5                  </dependency>
     3.6                  <dependency>
     3.7 +                    <code-name-base>org.netbeans.libs.lucene</code-name-base>
     3.8 +                    <build-prerequisite/>
     3.9 +                    <compile-dependency/>
    3.10 +                    <run-dependency>
    3.11 +                        <release-version>3</release-version>
    3.12 +                        <specification-version>3.8</specification-version>
    3.13 +                    </run-dependency>
    3.14 +                </dependency>
    3.15 +                <dependency>
    3.16 +                    <code-name-base>org.netbeans.modules.jackpot30.common</code-name-base>
    3.17 +                    <build-prerequisite/>
    3.18 +                    <compile-dependency/>
    3.19 +                    <run-dependency>
    3.20 +                        <specification-version>1.1</specification-version>
    3.21 +                    </run-dependency>
    3.22 +                </dependency>
    3.23 +                <dependency>
    3.24                      <code-name-base>org.netbeans.modules.options.api</code-name-base>
    3.25                      <build-prerequisite/>
    3.26                      <compile-dependency/>
    3.27 @@ -90,6 +107,14 @@
    3.28                      </run-dependency>
    3.29                  </dependency>
    3.30                  <dependency>
    3.31 +                    <code-name-base>org.openide.modules</code-name-base>
    3.32 +                    <build-prerequisite/>
    3.33 +                    <compile-dependency/>
    3.34 +                    <run-dependency>
    3.35 +                        <specification-version>7.33</specification-version>
    3.36 +                    </run-dependency>
    3.37 +                </dependency>
    3.38 +                <dependency>
    3.39                      <code-name-base>org.openide.nodes</code-name-base>
    3.40                      <build-prerequisite/>
    3.41                      <compile-dependency/>
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/remoting/ide/api/src/org/netbeans/modules/jackpot30/remoting/api/LocalCache.java	Wed Aug 08 18:39:56 2012 +0200
     4.3 @@ -0,0 +1,173 @@
     4.4 +/*
     4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 + *
     4.7 + * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
     4.8 + *
     4.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    4.10 + * Other names may be trademarks of their respective owners.
    4.11 + *
    4.12 + * The contents of this file are subject to the terms of either the GNU
    4.13 + * General Public License Version 2 only ("GPL") or the Common
    4.14 + * Development and Distribution License("CDDL") (collectively, the
    4.15 + * "License"). You may not use this file except in compliance with the
    4.16 + * License. You can obtain a copy of the License at
    4.17 + * http://www.netbeans.org/cddl-gplv2.html
    4.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    4.19 + * specific language governing permissions and limitations under the
    4.20 + * License.  When distributing the software, include this License Header
    4.21 + * Notice in each file and include the License file at
    4.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    4.23 + * particular file as subject to the "Classpath" exception as provided
    4.24 + * by Oracle in the GPL Version 2 section of the License file that
    4.25 + * accompanied this code. If applicable, add the following below the
    4.26 + * License Header, with the fields enclosed by brackets [] replaced by
    4.27 + * your own identifying information:
    4.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    4.29 + *
    4.30 + * If you wish your version of this file to be governed by only the CDDL
    4.31 + * or only the GPL Version 2, indicate your decision by adding
    4.32 + * "[Contributor] elects to include this software in this distribution
    4.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    4.34 + * single choice of license, a recipient has the option to distribute
    4.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    4.36 + * to extend the choice of license to its licensees as provided above.
    4.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    4.38 + * Version 2 license, then the option applies only if the new code is
    4.39 + * made subject to such option by the copyright holder.
    4.40 + *
    4.41 + * Contributor(s):
    4.42 + *
    4.43 + * Portions Copyrighted 2012 Sun Microsystems, Inc.
    4.44 + */
    4.45 +package org.netbeans.modules.jackpot30.remoting.api;
    4.46 +
    4.47 +import java.io.File;
    4.48 +import java.io.FileOutputStream;
    4.49 +import java.io.IOException;
    4.50 +import java.io.OutputStream;
    4.51 +import java.net.URI;
    4.52 +import java.net.URISyntaxException;
    4.53 +import java.util.AbstractMap.SimpleEntry;
    4.54 +import java.util.HashMap;
    4.55 +import java.util.LinkedHashMap;
    4.56 +import java.util.Map;
    4.57 +import java.util.Map.Entry;
    4.58 +import java.util.concurrent.atomic.AtomicBoolean;
    4.59 +import java.util.logging.Level;
    4.60 +import java.util.logging.Logger;
    4.61 +import org.apache.lucene.index.IndexReader;
    4.62 +import org.apache.lucene.index.IndexWriter;
    4.63 +import org.apache.lucene.index.IndexWriter.MaxFieldLength;
    4.64 +import org.apache.lucene.store.FSDirectory;
    4.65 +import org.codeviation.pojson.Pojson;
    4.66 +import org.netbeans.api.annotations.common.CheckForNull;
    4.67 +import org.netbeans.modules.jackpot30.common.api.IndexAccess.NoAnalyzer;
    4.68 +import org.netbeans.modules.jackpot30.remotingapi.CacheFolder;
    4.69 +import org.openide.filesystems.FileObject;
    4.70 +import org.openide.filesystems.FileUtil;
    4.71 +
    4.72 +/**
    4.73 + *
    4.74 + * @author lahvac
    4.75 + */
    4.76 +public class LocalCache {
    4.77 +
    4.78 +    private static final Map<URI, IndexReader> readerCache = new HashMap<URI, IndexReader>();
    4.79 +
    4.80 +    private static File findLocalCacheDir(RemoteIndex ri) throws IOException {
    4.81 +        return new File(FileUtil.toFile(FileUtil.createFolder(CacheFolder.getDataFolder(ri.remote), "remote-duplicates")), ri.remoteSegment);
    4.82 +    }
    4.83 +
    4.84 +    private static final long VERSION_CHECK_PERIOD = 60 * 60 * 1000;
    4.85 +    private static final Map<Entry<URI, String>, Long> lastVersionCheck = new HashMap<Entry<URI, String>, Long>();
    4.86 +
    4.87 +    @CheckForNull public static synchronized <R> R runOverLocalCache(RemoteIndex ri, Task<IndexReader, R> task, R empty, AtomicBoolean cancel) throws IOException, URISyntaxException {
    4.88 +        URI uri = ri.remote.toURI();
    4.89 +        SimpleEntry<URI, String> versionCheckKey = new SimpleEntry<URI, String>(uri, ri.remoteSegment);
    4.90 +        Long lastCheck = lastVersionCheck.get(versionCheckKey);
    4.91 +
    4.92 +        if (lastCheck == null || (System.currentTimeMillis() - lastCheck) > VERSION_CHECK_PERIOD) {
    4.93 +            File dir = findLocalCacheDir(ri);
    4.94 +            File remoteVersion = new File(dir, "remoteVersion");
    4.95 +            FileObject remoteVersionFO = FileUtil.toFileObject(remoteVersion);
    4.96 +            String previousVersion = remoteVersionFO != null ? remoteVersionFO.asText("UTF-8") : null;
    4.97 +            URI infoURI = new URI(ri.remote.toExternalForm() + "/info?path=" + WebUtilities.escapeForQuery(ri.remoteSegment));
    4.98 +            String infoContent = WebUtilities.requestStringResponse(infoURI, cancel);
    4.99 +
   4.100 +            if (cancel.get()) return empty;
   4.101 +
   4.102 +            if (infoContent != null) {
   4.103 +                Object buildId = Pojson.load(LinkedHashMap.class, infoContent).get("BUILD_ID");
   4.104 +
   4.105 +                if (buildId != null && !(buildId = buildId.toString()).equals(previousVersion)) {
   4.106 +                    remoteVersion.getParentFile().mkdirs();
   4.107 +                    OutputStream out = new FileOutputStream(remoteVersion);
   4.108 +                    try {
   4.109 +                        out.write(buildId.toString().getBytes("UTF-8"));
   4.110 +                    } finally {
   4.111 +                        out.close();
   4.112 +                    }
   4.113 +
   4.114 +                    LOG.log(Level.FINE, "Deleting local cache");
   4.115 +                    delete(new File(dir, "index"));
   4.116 +
   4.117 +                    IndexReader reader = readerCache.remove(uri);
   4.118 +                    if (reader != null)
   4.119 +                        reader.close();
   4.120 +
   4.121 +                }
   4.122 +            }
   4.123 +
   4.124 +            lastVersionCheck.put(versionCheckKey, System.currentTimeMillis());
   4.125 +        }
   4.126 +
   4.127 +        IndexReader reader = readerCache.get(uri);
   4.128 +
   4.129 +        if (reader == null && !cancel.get()) {
   4.130 +            File dir = new File(findLocalCacheDir(ri), "index");
   4.131 +
   4.132 +            if (dir.listFiles() != null && dir.listFiles().length > 0) {
   4.133 +                readerCache.put(uri, reader = IndexReader.open(FSDirectory.open(dir), true));
   4.134 +            }
   4.135 +        }
   4.136 +
   4.137 +        if (reader == null || cancel.get()) {
   4.138 +            return empty;
   4.139 +        }
   4.140 +
   4.141 +        return task.run(reader, cancel);
   4.142 +    }
   4.143 +
   4.144 +    public static synchronized void saveToLocalCache(RemoteIndex ri, Task<IndexWriter, Void> save) throws IOException, URISyntaxException {
   4.145 +        IndexReader r = readerCache.remove(ri.remote.toURI());
   4.146 +
   4.147 +        if (r != null) {
   4.148 +            r.close();
   4.149 +        }
   4.150 +
   4.151 +        IndexWriter w = new IndexWriter(FSDirectory.open(new File(findLocalCacheDir(ri), "index")), new NoAnalyzer(), MaxFieldLength.UNLIMITED);
   4.152 +
   4.153 +        save.run(w, new AtomicBoolean());
   4.154 +
   4.155 +        w.optimize();
   4.156 +        w.close();
   4.157 +    }
   4.158 +
   4.159 +    private static final Logger LOG = Logger.getLogger(LocalCache.class.getName());
   4.160 +
   4.161 +    private static void delete(File file) {
   4.162 +        File[] c = file.listFiles();
   4.163 +
   4.164 +        if (c != null) {
   4.165 +            for (File cc : c) {
   4.166 +                delete(cc);
   4.167 +            }
   4.168 +        }
   4.169 +
   4.170 +        file.delete();
   4.171 +    }
   4.172 +
   4.173 +    public interface Task<P, R> {
   4.174 +        public R run(P p, AtomicBoolean cancel) throws IOException;
   4.175 +    }
   4.176 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/remoting/ide/api/src/org/netbeans/modules/jackpot30/remotingapi/CacheFolder.java	Wed Aug 08 18:39:56 2012 +0200
     5.3 @@ -0,0 +1,269 @@
     5.4 +/*
     5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.6 + *
     5.7 + * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
     5.8 + *
     5.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    5.10 + * Other names may be trademarks of their respective owners.
    5.11 + *
    5.12 + * The contents of this file are subject to the terms of either the GNU
    5.13 + * General Public License Version 2 only ("GPL") or the Common
    5.14 + * Development and Distribution License("CDDL") (collectively, the
    5.15 + * "License"). You may not use this file except in compliance with the
    5.16 + * License. You can obtain a copy of the License at
    5.17 + * http://www.netbeans.org/cddl-gplv2.html
    5.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    5.19 + * specific language governing permissions and limitations under the
    5.20 + * License.  When distributing the software, include this License Header
    5.21 + * Notice in each file and include the License file at
    5.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    5.23 + * particular file as subject to the "Classpath" exception as provided
    5.24 + * by Oracle in the GPL Version 2 section of the License file that
    5.25 + * accompanied this code. If applicable, add the following below the
    5.26 + * License Header, with the fields enclosed by brackets [] replaced by
    5.27 + * your own identifying information:
    5.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    5.29 + *
    5.30 + * If you wish your version of this file to be governed by only the CDDL
    5.31 + * or only the GPL Version 2, indicate your decision by adding
    5.32 + * "[Contributor] elects to include this software in this distribution
    5.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    5.34 + * single choice of license, a recipient has the option to distribute
    5.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    5.36 + * to extend the choice of license to its licensees as provided above.
    5.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    5.38 + * Version 2 license, then the option applies only if the new code is
    5.39 + * made subject to such option by the copyright holder.
    5.40 + *
    5.41 + * Contributor(s):
    5.42 + *
    5.43 + * Portions Copyrighted 2008 Sun Microsystems, Inc.
    5.44 + */
    5.45 +
    5.46 +package org.netbeans.modules.jackpot30.remotingapi;
    5.47 +
    5.48 +import java.io.File;
    5.49 +import java.io.IOException;
    5.50 +import java.io.InputStream;
    5.51 +import java.io.OutputStream;
    5.52 +import java.net.URL;
    5.53 +import java.util.HashMap;
    5.54 +import java.util.LinkedList;
    5.55 +import java.util.List;
    5.56 +import java.util.Map;
    5.57 +import java.util.Map.Entry;
    5.58 +import java.util.Properties;
    5.59 +import java.util.logging.Level;
    5.60 +import java.util.logging.Logger;
    5.61 +import org.openide.filesystems.FileObject;
    5.62 +import org.openide.filesystems.FileSystem;
    5.63 +import org.openide.filesystems.FileUtil;
    5.64 +import org.openide.filesystems.URLMapper;
    5.65 +import org.openide.modules.Places;
    5.66 +import org.openide.util.Exceptions;
    5.67 +import org.openide.util.RequestProcessor;
    5.68 +
    5.69 +/**
    5.70 + *
    5.71 + * @author Tomas Zezula
    5.72 + */
    5.73 +public final class CacheFolder {
    5.74 +
    5.75 +    private static final Logger LOG = Logger.getLogger(CacheFolder.class.getName());
    5.76 +    private static final RequestProcessor RP = new RequestProcessor(CacheFolder.class.getName(), 1, false, false);
    5.77 +    private static final RequestProcessor.Task SAVER = RP.create(new Saver());
    5.78 +    private static final int SLIDING_WINDOW = 500;
    5.79 +
    5.80 +    private static final String SEGMENTS_FILE = "segments";      //NOI18N
    5.81 +    private static final String SLICE_PREFIX = "s";              //NOI18N
    5.82 +
    5.83 +    //@GuardedBy("CacheFolder.class")
    5.84 +    private static FileObject cacheFolder;
    5.85 +    //@GuardedBy("CacheFolder.class")
    5.86 +    private static Properties segments;
    5.87 +    //@GuardedBy("CacheFolder.class")
    5.88 +    private static Map<String, String> invertedSegments;
    5.89 +    //@GuardedBy("CacheFolder.class")
    5.90 +    private static int index = 0;
    5.91 +
    5.92 +
    5.93 +    //@NotThreadSafe
    5.94 +    @org.netbeans.api.annotations.common.SuppressWarnings(
    5.95 +        value="LI_LAZY_INIT_UPDATE_STATIC"
    5.96 +        /*,justification="Caller already holds a monitor"*/)
    5.97 +    private static void loadSegments(FileObject folder) throws IOException {
    5.98 +        assert Thread.holdsLock(CacheFolder.class);
    5.99 +        if (segments == null) {
   5.100 +            assert folder != null;
   5.101 +            segments = new Properties ();
   5.102 +            invertedSegments = new HashMap<String,String> ();
   5.103 +            final FileObject segmentsFile =  folder.getFileObject(SEGMENTS_FILE);
   5.104 +            if (segmentsFile!=null) {
   5.105 +                final InputStream in = segmentsFile.getInputStream();
   5.106 +                try {
   5.107 +                    segments.load (in);
   5.108 +                } finally {
   5.109 +                    in.close();
   5.110 +                }
   5.111 +            }
   5.112 +            for (Map.Entry entry : segments.entrySet()) {
   5.113 +                String segment = (String) entry.getKey();
   5.114 +                String root = (String) entry.getValue();
   5.115 +                invertedSegments.put(root,segment);
   5.116 +                try {
   5.117 +                    index = Math.max (index,Integer.parseInt(segment.substring(SLICE_PREFIX.length())));
   5.118 +                } catch (NumberFormatException nfe) {
   5.119 +                    LOG.log(Level.FINE, null, nfe);
   5.120 +                }
   5.121 +            }
   5.122 +        }
   5.123 +    }
   5.124 +
   5.125 +
   5.126 +    private static void storeSegments(FileObject folder) throws IOException {
   5.127 +        assert Thread.holdsLock(CacheFolder.class);
   5.128 +        assert folder != null;
   5.129 +        //It's safer to use FileUtil.createData(File) than FileUtil.createData(FileObject, String)
   5.130 +        //see issue #173094
   5.131 +        final File _file = FileUtil.toFile(folder);
   5.132 +        assert _file != null;
   5.133 +        final FileObject segmentsFile = FileUtil.createData(new File(_file, SEGMENTS_FILE));
   5.134 +        final OutputStream out = segmentsFile.getOutputStream();
   5.135 +        try {
   5.136 +            segments.store(out,null);
   5.137 +        } finally {
   5.138 +            out.close();
   5.139 +        }
   5.140 +    }
   5.141 +
   5.142 +    public static synchronized URL getSourceRootForDataFolder (final FileObject dataFolder) {
   5.143 +        final FileObject segFolder = dataFolder.getParent();
   5.144 +        if (segFolder == null || !segFolder.equals(cacheFolder)) {
   5.145 +            return null;
   5.146 +        }
   5.147 +        String source = segments.getProperty(dataFolder.getName());
   5.148 +        if (source != null) {
   5.149 +            try {
   5.150 +                return new URL (source);
   5.151 +            } catch (IOException ioe) {
   5.152 +                LOG.log(Level.FINE, null, ioe);
   5.153 +            }
   5.154 +        }
   5.155 +        return null;
   5.156 +    }
   5.157 +
   5.158 +    public static FileObject getDataFolder (final URL root) throws IOException {
   5.159 +        return getDataFolder(root, false);
   5.160 +    }
   5.161 +
   5.162 +    public static FileObject getDataFolder (final URL root, final boolean onlyIfAlreadyExists) throws IOException {
   5.163 +        final String rootName = root.toExternalForm();
   5.164 +        final FileObject _cacheFolder = getCacheFolder();
   5.165 +        String slice;
   5.166 +        synchronized (CacheFolder.class) {
   5.167 +            loadSegments(_cacheFolder);
   5.168 +            slice = invertedSegments.get (rootName);
   5.169 +            if (slice == null) {
   5.170 +                if (onlyIfAlreadyExists) {
   5.171 +                    return null;
   5.172 +                }
   5.173 +                slice = SLICE_PREFIX + (++index);
   5.174 +                while (segments.getProperty(slice) != null) {
   5.175 +                    slice = SLICE_PREFIX + (++index);
   5.176 +                }
   5.177 +                segments.put (slice,rootName);
   5.178 +                invertedSegments.put(rootName, slice);
   5.179 +                SAVER.schedule(SLIDING_WINDOW);
   5.180 +            }
   5.181 +        }
   5.182 +        assert slice != null;
   5.183 +        if (onlyIfAlreadyExists) {
   5.184 +            return cacheFolder.getFileObject(slice);
   5.185 +        } else {
   5.186 +            return FileUtil.createFolder(_cacheFolder, slice);
   5.187 +        }
   5.188 +    }
   5.189 +
   5.190 +    public static synchronized Iterable<? extends FileObject> findRootsWithCacheUnderFolder(FileObject folder) throws IOException {
   5.191 +        URL folderURL = folder.toURL();
   5.192 +        String prefix = folderURL.toExternalForm();
   5.193 +        final FileObject _cacheFolder = getCacheFolder();
   5.194 +        List<FileObject> result = new LinkedList<FileObject>();
   5.195 +        loadSegments(_cacheFolder);
   5.196 +        for (Entry<String, String> e : invertedSegments.entrySet()) {
   5.197 +            if (e.getKey().startsWith(prefix)) {
   5.198 +                FileObject fo = URLMapper.findFileObject(new URL(e.getKey()));
   5.199 +
   5.200 +                if (fo != null) {
   5.201 +                    result.add(fo);
   5.202 +                }
   5.203 +            }
   5.204 +        }
   5.205 +
   5.206 +        return result;
   5.207 +    }
   5.208 +
   5.209 +    public static synchronized FileObject getCacheFolder () {
   5.210 +        if (cacheFolder == null) {
   5.211 +            File cache = Places.getCacheSubdirectory("index"); // NOI18N
   5.212 +            if (!cache.isDirectory()) {
   5.213 +                throw new IllegalStateException("Indices cache folder " + cache.getAbsolutePath() + " is not a folder"); //NOI18N
   5.214 +            }
   5.215 +            if (!cache.canRead()) {
   5.216 +                throw new IllegalStateException("Can't read from indices cache folder " + cache.getAbsolutePath()); //NOI18N
   5.217 +            }
   5.218 +            if (!cache.canWrite()) {
   5.219 +                throw new IllegalStateException("Can't write to indices cache folder " + cache.getAbsolutePath()); //NOI18N
   5.220 +            }
   5.221 +
   5.222 +            cacheFolder = FileUtil.toFileObject(cache);
   5.223 +            if (cacheFolder == null) {
   5.224 +                throw new IllegalStateException("Can't convert indices cache folder " + cache.getAbsolutePath() + " to FileObject"); //NOI18N
   5.225 +            }
   5.226 +        }
   5.227 +        return cacheFolder;
   5.228 +    }
   5.229 +
   5.230 +
   5.231 +    /**
   5.232 +     * Only for unit tests! It's used also by CslTestBase, which is not in the
   5.233 +     * same package, hence the public keyword.
   5.234 +     *
   5.235 +     */
   5.236 +    public static void setCacheFolder (final FileObject folder) {
   5.237 +        SAVER.schedule(0);
   5.238 +        SAVER.waitFinished();
   5.239 +        synchronized (CacheFolder.class) {
   5.240 +            assert folder != null && folder.canRead() && folder.canWrite();
   5.241 +            cacheFolder = folder;
   5.242 +            segments = null;
   5.243 +            invertedSegments = null;
   5.244 +            index = 0;
   5.245 +        }
   5.246 +    }
   5.247 +
   5.248 +    private CacheFolder() {
   5.249 +        // no-op
   5.250 +    }
   5.251 +
   5.252 +    private static class Saver implements Runnable {
   5.253 +        @Override
   5.254 +        public void run() {
   5.255 +            try {
   5.256 +                final FileObject cf = getCacheFolder();
   5.257 +                // #170182 - preventing filesystem events being fired from under the CacheFolder.class lock
   5.258 +                cf.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
   5.259 +                    @Override
   5.260 +                    public void run() throws IOException {
   5.261 +                        synchronized (CacheFolder.class) {
   5.262 +                            if (segments == null) return ;
   5.263 +                            storeSegments(cf);
   5.264 +                        }
   5.265 +                    }
   5.266 +                });
   5.267 +            } catch (IOException ioe) {
   5.268 +                Exceptions.printStackTrace(ioe);
   5.269 +            }
   5.270 +        }
   5.271 +    }
   5.272 +}