/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package org.eclipse.dltk.core.search.index; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.Path; import org.eclipse.dltk.compiler.CharOperation; import org.eclipse.dltk.compiler.util.HashtableOfObject; import org.eclipse.dltk.compiler.util.ObjectVector; import org.eclipse.dltk.compiler.util.SimpleSet; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.search.indexing.IIndexConstants; import org.eclipse.dltk.core.search.indexing.IndexManager; import org.eclipse.dltk.internal.core.util.Util; public class MixinIndex extends Index { private static final char[] OLD_HEADER = "MIXIN INDEX 0.1".toCharArray(); //$NON-NLS-1$ private static final char[] OLD_HEADER_2 = "MIXIN INDEX 0.2".toCharArray(); //$NON-NLS-1$ private static final char[] HEADER = "MIXIN INDEX 0.3".toCharArray(); //$NON-NLS-1$ private final HashtableOfObject keyToDocs = new HashtableOfObject(10); private final SimpleSet documentNames = new SimpleSet(10); private final String fileName; private boolean dirty; public MixinIndex(String fileName, String containerPath, boolean reuseFile) throws IOException { super(fileName, containerPath); this.fileName = fileName; this.dirty = false; if (reuseFile) { initialize(reuseFile); } else { save(); } } @Override public void addIndexEntry(char[] category, char[] key, String containerRelativePath) { this.dirty = true; Assert.isTrue(CharOperation.equals(category, IIndexConstants.MIXIN)); if (false) { System.out.println("MIXIN: addIndexEntry '" + new String(key) //$NON-NLS-1$ + "' path '" + containerRelativePath + "'"); //$NON-NLS-1$ //$NON-NLS-2$ } addIndexEntry(key, internDocName(containerRelativePath)); } /** * Adds document to the index without any associated words. This method is * needed to save in the index names of all indexed documents - some * documents contains no MIXIN-related information, so this method is used * to record just the document name. * * @param containerRelativePath */ public void addDocumentName(String containerRelativePath) { final int savedCount = documentNames.elementSize; internDocName(containerRelativePath); if (documentNames.elementSize > savedCount) { dirty = true; } } private void addIndexEntry(char[] key, String containerRelativePath) { SimpleSet docs = (SimpleSet) keyToDocs.get(key); if (docs == null) { docs = new SimpleSet(1); keyToDocs.put(key, docs); } docs.add(containerRelativePath); } @Override public File getIndexFile() { return new File(fileName); } @Override public boolean hasChanged() { return this.dirty; } private static boolean isMixinCategory(char[][] categories) { for (int i = 0; i < categories.length; i++) { if (CharOperation.equals(categories[i], IIndexConstants.MIXIN)) { return true; } } return false; } @Override public EntryResult[] query(char[][] categories, char[] key, int matchRule) throws IOException { if (!isMixinCategory(categories)) return new EntryResult[0]; final ObjectVector results = new ObjectVector(); performQuery(key, matchRule, results); final EntryResult[] entryResults = new EntryResult[results.size]; results.copyInto(entryResults); return entryResults; } private void performQuery(char[] key, int matchRule, ObjectVector results) { final char[][] keyTable = keyToDocs.keyTable; for (int i = 0, keyLen = keyTable.length; i < keyLen; i++) { final char[] nextKey = keyTable[i]; if (nextKey == null) continue; if (Index.isMatch(key, nextKey, matchRule)) { final EntryResult s = new EntryResult(nextKey, null); results.add(s); final Object[] docTable = ((SimpleSet) keyToDocs.valueTable[i]).values; for (int j = 0, docLen = docTable.length; j < docLen; j++) { final String doc = (String) docTable[j]; if (doc != null) { s.addDocumentName(doc); } } } } } private static String[] extractKeysFromTable(SimpleSet table, String substring) { String[] documentNames = new String[table.elementSize]; int count = 0; final Object[] values = table.values; for (int i = 0, l = values.length; i < l; i++) { final String result = (String) values[i]; if (result != null && (substring == null || result.startsWith(substring))) { documentNames[count++] = result; } } if (count != documentNames.length) { final String[] result = new String[count]; System.arraycopy(documentNames, 0, result, 0, count); documentNames = result; } return documentNames; } /** * Returns the document names that contain the given substring, if null then * returns all of them. */ @Override public String[] queryDocumentNames(String substring) throws IOException { return extractKeysFromTable(documentNames, substring); } @Override public void remove(String containerRelativePath) { this.dirty = true; if (documentNames.remove(containerRelativePath) != null) { final char[][] keyTable = keyToDocs.keyTable; for (int i = 0; i < keyTable.length; i++) { final SimpleSet docs = (SimpleSet) keyToDocs.valueTable[i]; if (docs != null) { docs.remove(containerRelativePath); } } } } @Override public void save() throws IOException { long start = DLTKCore.VERBOSE_MIXIN ? System.currentTimeMillis() : 0; if (!hasChanged()) { return; } File f = getIndexFile(); FileOutputStream fouts = new FileOutputStream(f, false); BufferedOutputStream bufout = new BufferedOutputStream(fouts, 2048); DataOutputStream stream = new DataOutputStream(bufout); final SimpleSet allDocuments = new SimpleSet(); allDocuments.addAll(documentNames); Util.writeUTF(stream, HEADER); int keyCount = keyToDocs.elementSize; stream.writeInt(keyCount); for (int i = 0; i < keyToDocs.keyTable.length; i++) { char[] key = keyToDocs.keyTable[i]; if (key == null) continue; Util.writeUTF(stream, key); final SimpleSet docs = (SimpleSet) keyToDocs.valueTable[i]; stream.writeInt(docs.elementSize); for (int j = 0; j < docs.values.length; j++) { final String docName = (String) docs.values[j]; if (docName != null) { Util.writeUTF(stream, docName.toCharArray()); allDocuments.remove(docName); } } } stream.writeInt(allDocuments.size()); for (int i = 0, docTableLen = allDocuments.values.length; i < docTableLen; ++i) { String docName = (String) allDocuments.values[i]; if (docName != null) { Util.writeUTF(stream, docName.toCharArray()); } } bufout.close(); stream.close(); fouts.close(); this.dirty = false; if (DLTKCore.VERBOSE_MIXIN) { System.out.println("Mixin index for " + this.containerPath + " (" //$NON-NLS-1$ //$NON-NLS-2$ + new Path(this.fileName).lastSegment() + ") saved, took " //$NON-NLS-1$ + (System.currentTimeMillis() - start)); System.out.println("Mixin modules: " + this.documentNames.size()); //$NON-NLS-1$ System.out.println("Mixin keys: " + this.keyToDocs.size()); //$NON-NLS-1$ } } private void initialize(boolean reuseExistingFile) throws IOException { boolean successful = false; File indexFile = getIndexFile(); if (indexFile.exists()) { if (reuseExistingFile) { try { monitor.enterRead(); DataInputStream stream = new DataInputStream( new BufferedInputStream(new FileInputStream( indexFile), 8192)); try { final char[] header = Util.readUTF(stream); if (CharOperation.equals(OLD_HEADER, header)) { loadDocToKeyFormat(stream); successful = true; } else if (CharOperation.equals(OLD_HEADER_2, header) || CharOperation.equals(HEADER, header)) { loadKeyToDocFormat(stream); successful = true; } } finally { stream.close(); } } catch (FileNotFoundException e) { if (DLTKCore.DEBUG_INDEX) e.printStackTrace(); } catch (IOException e) { if (DLTKCore.DEBUG_INDEX) e.printStackTrace(); } finally { monitor.exitRead(); } if (successful) return; } if (!indexFile.delete()) { if (DLTKCore.DEBUG_INDEX) System.out .println("initialize - Failed to delete mixin index " + this.fileName); //$NON-NLS-1$ throw new IOException( "Failed to delete mixin index " + this.fileName); //$NON-NLS-1$ } } if (indexFile.createNewFile()) { save(); } else { if (DLTKCore.DEBUG_INDEX) System.out .println("initialize - Failed to create new index " + this.fileName); //$NON-NLS-1$ throw new IOException("Failed to create new index " + this.fileName); //$NON-NLS-1$ } this.dirty = false; } private void loadKeyToDocFormat(DataInputStream stream) throws IOException { final int keyCount = stream.readInt(); for (int i = 0; i < keyCount; i++) { final char[] key = Util.readUTF(stream); final int docCount = stream.readInt(); for (int j = 0; j < docCount; j++) { String docName = internDocName(new String(Util.readUTF(stream))); addIndexEntry(key, docName); } } final int docCount = stream.readInt(); for (int i = 0; i < docCount; ++i) { internDocName(new String(Util.readUTF(stream))); } } private void loadDocToKeyFormat(DataInputStream stream) throws IOException { int documentsCount = stream.readInt(); for (int i = 0; i < documentsCount; i++) { String docName = internDocName(new String(Util.readUTF(stream))); int wordsCount = stream.readInt(); if (wordsCount > 0) { for (int j = 0; j < wordsCount; j++) { char[] word = Util.readUTF(stream); addIndexEntry(word, docName); } } } } private final String internDocName(String docName) { return (String) documentNames.addIntern(docName); } @Override public void startQuery() { } @Override public void stopQuery() { } @Override public String toString() { return "Mixin Index for " + this.containerPath; //$NON-NLS-1$ } @Override public boolean isRebuildable() { return false; } @Override public String getContainerPath() { if (containerPath.startsWith(IndexManager.SPECIAL_MIXIN)) { return containerPath.substring(IndexManager.SPECIAL_MIXIN.length()); } else { return containerPath; } } }