001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.gsfret.source.usages;
042:
043: import java.io.BufferedInputStream;
044: import java.io.BufferedOutputStream;
045: import java.io.File;
046: import java.io.FileInputStream;
047: import java.io.FileOutputStream;
048: import java.io.IOException;
049: import java.io.InputStream;
050: import java.io.OutputStream;
051: import java.net.URISyntaxException;
052: import java.net.URL;
053: import java.util.ArrayList;
054: import java.util.HashMap;
055: import java.util.List;
056: import java.util.Map;
057: import java.util.Properties;
058: import java.util.zip.ZipEntry;
059: import java.util.zip.ZipInputStream;
060: import java.util.zip.ZipOutputStream;
061: import org.netbeans.modules.gsf.api.IndexDocument;
062: import org.netbeans.modules.gsf.api.Indexer;
063: import org.netbeans.modules.gsf.Language;
064: import org.netbeans.modules.gsf.LanguageRegistry;
065: import org.openide.ErrorManager;
066: import org.openide.filesystems.FileLock;
067: import org.openide.filesystems.FileObject;
068: import org.openide.filesystems.FileSystem;
069: import org.openide.filesystems.FileUtil;
070: import org.openide.filesystems.URLMapper;
071: import org.openide.util.Exceptions;
072:
073: /**
074: * This file is originally from Retouche, the Java Support
075: * infrastructure in NetBeans. I have modified the file as little
076: * as possible to make merging Retouche fixes back as simple as
077: * possible.
078: *
079: * Index SPI. Represents an index for usages data
080: * @author Tomas Zezula
081: */
082: // BEGIN TOR MODIFICATIONS
083: public abstract class Index extends org.netbeans.modules.gsf.api.Index {
084: protected Language language;
085:
086: protected Index(Language language) {
087: this .language = language;
088: }
089:
090: // END TOR MODIFICATIONS
091:
092: public enum BooleanOperator {
093: AND, OR
094: };
095:
096: private static final int VERSION = 1;
097: private static final int SUBVERSION = 118;
098: private static final String NB_USER_DIR = "netbeans.user"; //NOI18N
099: private static final String SEGMENTS_FILE = "segments"; //NOI18N
100: private static final String CLASSES = "classes"; //NOI18N
101: private static final String SLICE_PREFIX = "s"; //NOI18N
102: private static final String INDEX_DIR = "var" + File.separatorChar
103: + "cache" + File.separatorChar + "gsf-index"
104: + File.separatorChar + VERSION + '.' + SUBVERSION; //NOI18N
105: // BEGIN TOR MODIFICATIONS
106: protected static final String PREINDEXED = "netbeans-index-"; // NOI18N
107: private static final String PREINDEXED_MARKER = "static";
108: private static final boolean COMPUTE_INDEX = Boolean
109: .getBoolean("ruby.computeindex");
110:
111: public abstract Map<String, String> getTimeStamps()
112: throws IOException;
113:
114: // Store map of class names, where each entry has a map of fields and values (fields might be "name", "fqn", "case insensitive name", etc.
115: // The same fields can be looked up later.
116: public abstract void store(String fileUrl,
117: List<IndexDocument> documents) throws IOException;
118:
119: // END TOR MODIFICATIONS
120:
121: public abstract boolean isValid(boolean tryOpen) throws IOException;
122:
123: public abstract boolean isUpToDate(String resourceName,
124: long timeStamp) throws IOException;
125:
126: public abstract void clear() throws IOException;
127:
128: public abstract void close() throws IOException;
129:
130: private static class LanguageContext {
131: private Properties segments;
132: private Map<String, String> invertedSegments;
133: private File cacheFolder;
134: private File segmentsFile;
135: private int index = 0;
136: private Language language;
137:
138: private LanguageContext(Language language) {
139: this .language = language;
140: }
141:
142: private void loadSegments() throws IOException {
143: if (segments == null) {
144: File cacheFolder = getCacheFolder();
145: assert cacheFolder != null;
146: segments = new Properties();
147: invertedSegments = new HashMap<String, String>();
148: segmentsFile = FileUtil.normalizeFile(new File(
149: cacheFolder, SEGMENTS_FILE));
150: if (segmentsFile.exists()) {
151: InputStream in = new FileInputStream(segmentsFile);
152: try {
153: segments.load(in);
154: } finally {
155: in.close();
156: }
157: }
158: for (Map.Entry entry : segments.entrySet()) {
159: String segment = (String) entry.getKey();
160: String root = (String) entry.getValue();
161: invertedSegments.put(root, segment);
162: try {
163: index = Math.max(index, Integer
164: .parseInt(segment
165: .substring(SLICE_PREFIX
166: .length())));
167: } catch (NumberFormatException nfe) {
168: ErrorManager.getDefault().notify(nfe);
169: }
170: }
171: assert segmentsFile != null;
172: }
173: }
174:
175: private void storeSegments() throws IOException {
176: assert segmentsFile != null;
177: OutputStream out = new FileOutputStream(segmentsFile);
178: try {
179: segments.store(out, null);
180: } finally {
181: out.close();
182: }
183: }
184:
185: private synchronized File getCacheFolder() {
186: if (cacheFolder == null) {
187: final String nbUserDirProp = getNbUserDir();
188: assert nbUserDirProp != null;
189: final File nbUserDir = new File(nbUserDirProp);
190: cacheFolder = FileUtil.normalizeFile(new File(
191: nbUserDir, INDEX_DIR));
192: Indexer indexer = language.getIndexer();
193: assert indexer != null : language;
194: cacheFolder = new File(cacheFolder, indexer
195: .getIndexerName()
196: + File.separator + indexer.getIndexVersion());
197: if (!cacheFolder.exists()) {
198: boolean created = cacheFolder.mkdirs();
199: assert created : "Cannot create cache folder"; //NOI18N
200: } else {
201: assert cacheFolder.isDirectory()
202: && cacheFolder.canRead()
203: && cacheFolder.canWrite();
204: }
205: }
206: return cacheFolder;
207: }
208:
209: /**
210: * Only for unit tests!
211: *
212: */
213: synchronized void setCacheFolder(final File folder) {
214: assert folder != null && folder.exists()
215: && folder.canRead() && folder.canWrite();
216: cacheFolder = folder;
217: }
218:
219: }
220:
221: // BEGIN TOR MODIFICTIONS
222: private static Map<Language, LanguageContext> contexts = new HashMap<Language, Index.LanguageContext>();
223:
224: static synchronized LanguageContext getContext(Language language) {
225: LanguageContext context = contexts.get(language);
226: if (context == null) {
227: context = new LanguageContext(language);
228: contexts.put(language, context);
229: }
230:
231: return context;
232: }
233:
234: // END TOR MODIFICTIONS
235:
236: public static URL getSourceRootForClassFolder(
237: final Language language, final URL classFolder) {
238: if ("file".equals(classFolder.getProtocol())) { //NOI18N
239: try {
240: final File file = FileUtil.normalizeFile(new File(
241: classFolder.toURI()));
242: final File segFolder = file.getParentFile();
243: if (segFolder == null) {
244: return null;
245: }
246: LanguageContext context = getContext(language);
247: final Object cFolder = segFolder.getParentFile();
248: if (cFolder == null
249: || !cFolder.equals(context.cacheFolder)) {
250: return null;
251: }
252: String source = context.segments.getProperty(segFolder
253: .getName());
254: if (source != null) {
255: try {
256: return new URL(source);
257: } catch (IOException ioe) {
258: ErrorManager.getDefault().notify(ioe);
259: }
260: }
261: } catch (URISyntaxException e) {
262: ErrorManager.getDefault().notify(e);
263: }
264: }
265: return null;
266: }
267:
268: // BEGIN TOR MODIFICATIONS
269: private static List<FileObject> preindexRoots;
270:
271: public static void addPreindexRoot(FileObject root) {
272: getPreindexRoots();
273: if (!preindexRoots.contains(root)) {
274: preindexRoots.add(root);
275: }
276: }
277:
278: private static List<FileObject> getPreindexRoots() {
279: if (preindexRoots == null) {
280: preindexRoots = new ArrayList<FileObject>();
281: // Add in the libraries
282: for (FileObject fo : LanguageRegistry.getInstance()
283: .getLibraryFos()) {
284: preindexRoots.add(fo);
285: }
286: }
287:
288: return preindexRoots;
289: }
290:
291: // END TOR MODIFICATIONS
292:
293: public static synchronized File getDataFolder(Language language,
294: final URL root) throws IOException {
295: // BEGIN TOR MODIFICTIONS
296: //loadSegments ();
297: LanguageContext context = getContext(language);
298: context.loadSegments();
299: // END TOR MODIFICTIONS
300: final String rootName = root.toExternalForm();
301: String slice = context.invertedSegments.get(rootName);
302: // BEGIN TOR MODIFICATIONS
303: FileObject extract = null;
304: // END TOR MODIFICATIONS
305: if (slice == null) {
306: slice = SLICE_PREFIX + (++context.index);
307: while (context.segments.getProperty(slice) != null) {
308: slice = SLICE_PREFIX + (++context.index);
309: }
310: context.segments.put(slice, rootName);
311: context.invertedSegments.put(rootName, slice);
312:
313: // BEGIN TOR MODIFICATIONS
314: // See if I have pre-indexed data for this file
315: FileObject rootFo = URLMapper.findFileObject(root);
316: if (rootFo != null) {
317: Indexer indexer = language.getIndexer();
318: String indexedFileName = PREINDEXED
319: + indexer.getIndexerName() + "-"
320: + indexer.getIndexVersion();
321: extract = rootFo.getFileObject(indexedFileName, "zip"); // NOI18N
322: if (extract == null && !COMPUTE_INDEX) {
323: // There's no co-located index data, but perhaps we have
324: // it within the larger preindexed bundles (these are
325: // zip files which contain a bunch of indices for other
326: // versions, such as native ruby 1.8.6, rails 1.1.6, 1.2.3, etc.
327:
328: // Compute relative path
329: rootSearch: for (FileObject fo : getPreindexRoots()) {
330: if (FileUtil.isParentOf(fo, rootFo)) {
331: // getRelativePath performs a isParentOf check and returns null if not
332: String relative = FileUtil.getRelativePath(
333: fo, rootFo);
334: if (relative != null
335: && relative.length() > 0) {
336: FileObject db = indexer
337: .getPreindexedDb();
338: if (db != null) {
339: extract = db.getFileObject(relative
340: + "/" + indexedFileName
341: + ".zip"); // NOI18N
342: }
343: break rootSearch;
344: }
345: }
346: }
347: }
348: }
349: // END TOR MODIFICATIONS
350: context.storeSegments();
351: }
352: File result = FileUtil.normalizeFile(new File(
353: context.cacheFolder, slice));
354: if (!result.exists()) {
355: result.mkdir();
356: // BEGIN TOR MODIFICATIONS
357: if (extract != null) {
358: File extractFile = FileUtil.toFile(extract);
359: FileObject dest = FileUtil.toFileObject(result);
360: if (dest != null) {
361: extractZip(dest, new BufferedInputStream(
362: new FileInputStream(extractFile)));
363: }
364: }
365: // END TOR MODIFICATIONS
366: }
367: return result;
368: }
369:
370: // BEGIN TOR MODIFICATIONS
371: static boolean isPreindexed(File dataDir) {
372: return new File(dataDir, PREINDEXED_MARKER).exists();
373: }
374:
375: // Based on openide/fs' FileUtil.extractJar
376: // NOTE: This code is already duplicated in the Ruby editing module, in the NbUtilities module
377: private static void extractZip(final FileObject fo,
378: final InputStream is) throws IOException {
379: FileSystem fs = fo.getFileSystem();
380:
381: fs.runAtomicAction(new FileSystem.AtomicAction() {
382: public void run() throws IOException {
383: extractZipImpl(fo, is);
384: }
385: });
386: }
387:
388: /** Does the actual extraction of the Jar file.
389: */
390: // Based on openide/fs' FileUtil.extractJarImpl
391: private static void extractZipImpl(FileObject fo, InputStream is)
392: throws IOException {
393: ZipEntry je;
394:
395: ZipInputStream jis = new ZipInputStream(is);
396:
397: while ((je = jis.getNextEntry()) != null) {
398: String name = je.getName();
399:
400: if (name.toLowerCase().startsWith("meta-inf/")) {
401: continue; // NOI18N
402: }
403:
404: if (je.isDirectory()) {
405: FileUtil.createFolder(fo, name);
406:
407: continue;
408: }
409:
410: // copy the file
411: FileObject fd = FileUtil.createData(fo, name);
412: FileLock lock = fd.lock();
413:
414: try {
415: OutputStream os = fd.getOutputStream(lock);
416:
417: try {
418: FileUtil.copy(jis, os);
419: } finally {
420: os.close();
421: }
422: } finally {
423: lock.releaseLock();
424: }
425: }
426: }
427:
428: // Only done at build time / ahead of time.
429: static void preindex(Language language, URL root) {
430: try {
431: FileObject rootFo = URLMapper.findFileObject(root);
432: File dataFile = getDataFolder(language, root);
433: // Create "preindexed" file
434: // Zip contents of data folder up and store it as a preindexed file in the rootFo
435: Indexer indexer = language.getIndexer();
436: String indexedFileName = PREINDEXED
437: + indexer.getIndexerName() + "-"
438: + indexer.getIndexVersion();
439: File output = new File(FileUtil.toFile(rootFo),
440: indexedFileName + ".zip"); // NOI18N
441: OutputStream os = new BufferedOutputStream(
442: new FileOutputStream(output));
443:
444: ZipEntry je;
445:
446: ZipOutputStream jis = new ZipOutputStream(os);
447:
448: je = new ZipEntry(PREINDEXED_MARKER);
449: jis.putNextEntry(je);
450: jis.closeEntry();
451:
452: File gsf = new File(dataFile, LuceneIndex.REFERENCES); // NOI18N
453: assert gsf.exists();
454:
455: ClassIndexImpl classIndexImpl = ClassIndexManager.get(
456: language).getUsagesQuery(root);
457: classIndexImpl.close();
458: if (gsf.list().length == 0) {
459: // Hmmm, empty... We need -something- in there to avoid reindexing next time
460: classIndexImpl.storeEmpty();
461: classIndexImpl.close();
462: }
463:
464: File[] files = gsf.listFiles();
465: for (File f : files) {
466: ZipEntry ze = new ZipEntry(LuceneIndex.REFERENCES + "/"
467: + f.getName()); // NOI18N
468: jis.putNextEntry(ze);
469:
470: // Copy data
471: InputStream is = new BufferedInputStream(
472: new FileInputStream(f));
473: FileUtil.copy(is, jis);
474:
475: jis.closeEntry();
476: }
477:
478: jis.finish();
479: jis.close();
480: } catch (IOException ioe) {
481: Exceptions.printStackTrace(ioe);
482: }
483: }
484:
485: // END TOR MODIFICATIONS
486:
487: /**
488: * Returns non cached netbeans user dir.
489: * For performance reasons the returned {@link File} is not normalized.
490: * Client is responsible to call {@link FileUtil.normalizeFile}
491: * before using the returned value.
492: * @return netbeans user dir.
493: */
494: static String getNbUserDir() {
495: final String nbUserProp = System.getProperty(NB_USER_DIR);
496: return nbUserProp;
497: }
498: }
|