/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.php.internal.indexer; import gnu.trove.map.hash.THashMap; import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.map.hash.TObjectLongHashMap; import gnu.trove.map.hash.TShortObjectHashMap; import gnu.trove.procedure.TObjectProcedure; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import com.aptana.core.logging.IdeLog; import com.aptana.editor.php.PHPEditorPlugin; import com.aptana.editor.php.indexer.IElementEntry; import com.aptana.editor.php.indexer.IElementsIndex; import com.aptana.editor.php.internal.contentAssist.ContentAssistUtils; import com.aptana.editor.php.internal.core.builder.IModule; /** * Unpacked element index. * * @author Denis Denisenko */ public class UnpackedElementIndex implements IModifiableElementsIndex { /** * Entries. */ protected THashMap<IModule, List<UnpackedEntry>> entries = new THashMap<IModule, List<UnpackedEntry>>(); private TObjectLongHashMap<IModule> timeStamps = new TObjectLongHashMap<IModule>(); /** * Category->Path->Entries map. Value might be represented by the single entry or by the entries list. */ private TIntObjectHashMap<THashMap<String, Object>> pathToEntries = new TIntObjectHashMap<THashMap<String, Object>>(); /** * Encoded first character of the path->Path->Entries map. */ private TIntObjectHashMap<TShortObjectHashMap<Object>> firstCharToEntries = new TIntObjectHashMap<TShortObjectHashMap<Object>>(); /** * Encoded first two characters of the path->Path->Entries map. */ private TIntObjectHashMap<TIntObjectHashMap<Object>> firstTwoCharsToEntries = new TIntObjectHashMap<TIntObjectHashMap<Object>>(); /** * Buffer used for conversions. */ private ByteBuffer converter = ByteBuffer.allocate(4); public void recordTimeStamp(IModule m, long timeStamp) { try { timeStamps.put(m, timeStamp); } catch (Exception e) { IdeLog.logWarning(PHPEditorPlugin.getDefault(), "Error recording timestamp for " + m.getFullPath(), e, PHPEditorPlugin.INDEXER_SCOPE); //$NON-NLS-1$ } } public long getTimeStamp(IModule m) { return timeStamps.get(m); } /** * {@inheritDoc} */ public synchronized IElementEntry addEntry(int category, String entryPath, Object value, IModule module) { UnpackedEntry entry = new UnpackedEntry(category, entryPath, value, module); // adding entry to the list of a module's entries addEntryToModuleList(module, entry); // adding entry to path->entry map addEntryToPathToEntriesMap(entry); // adding entry to first path character->entry map addEntryToFirstCharToEntriesMap(entry); // adding entry to first 2 path characters->entry map addEntryToFirstTwoCharsToEntriesMap(entry); return entry; } /** * {@inheritDoc} */ public synchronized void removeModuleEntries(IModule module) { List<UnpackedEntry> entriesToRemove = entries.get(module); if (entriesToRemove != null && !entriesToRemove.isEmpty()) { for (UnpackedEntry entryToRemove : entriesToRemove) { // System.out.println("Removing entry: " + entryToRemove); removeEntriesFromPathToEntries(entryToRemove); removeEntriesFromFirstCharToEntries(entryToRemove); removeEntriesFromFirstTwoCharcToEntries(entryToRemove); } } timeStamps.remove(module); entries.remove(module); // List<IElementEntry> curr = this.getEntriesStartingWith(-1, "C"); // for (IElementEntry e : curr) // { // System.out.println("After removing: " + e); // } } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public synchronized List<IElementEntry> getEntriesStartingWith(int category, String path) { int indexOf = path.lastIndexOf('\\'); String namespace = null; boolean filterByNamespace = ContentAssistUtils.isFilterByNamespace(); if (indexOf != -1) { namespace = path.substring(0, indexOf); path = path.substring(indexOf + 1); } final String lowerCasepath = path.toLowerCase(); final List<IElementEntry> toReturn = new ArrayList<IElementEntry>(); // handling empty path if (lowerCasepath.length() == 0) { if (category == IElementsIndex.ANY_CETEGORY) { entries.forEachValue(new TObjectProcedure<List<UnpackedEntry>>() { public boolean execute(List<UnpackedEntry> list) { toReturn.addAll(list); return true; } }); } else { TShortObjectHashMap<Object> map = firstCharToEntries.get(category); if (map != null) { map.forEachValue(new TObjectProcedure<Object>() { public boolean execute(Object obj) { if (obj instanceof IElementEntry) { toReturn.add((IElementEntry) obj); } else if (obj instanceof Collection) { toReturn.addAll((Collection<IElementEntry>) obj); } return true; } }); } } } // handling single character path else if (lowerCasepath.length() == 1) { final short firstCharacter = (short) lowerCasepath.charAt(0); if (category == IElementsIndex.ANY_CETEGORY) { firstCharToEntries.forEachValue(new TObjectProcedure<TShortObjectHashMap<Object>>() { public boolean execute(TShortObjectHashMap<Object> map) { Object objResult = map.get(firstCharacter); if (objResult != null) { addObjRefToList(toReturn, objResult); } return true; } }); } else { TShortObjectHashMap<Object> map = firstCharToEntries.get(category); if (map != null) { Object objResult = map.get(firstCharacter); if (objResult != null) { addObjRefToList(toReturn, objResult); } } } } // handling two characters path else if (lowerCasepath.length() == 2) { final int key = getIntKey(lowerCasepath); if (category == IElementsIndex.ANY_CETEGORY) { firstTwoCharsToEntries.forEachValue(new TObjectProcedure<TIntObjectHashMap<Object>>() { public boolean execute(TIntObjectHashMap<Object> map) { Object objResult = map.get(key); if (objResult != null) { addObjRefToList(toReturn, objResult); } return true; } }); } else { TIntObjectHashMap<Object> map = firstTwoCharsToEntries.get(category); if (map != null) { Object objResult = map.get(key); if (objResult != null) { addObjRefToList(toReturn, objResult); } } } } // handling more then two characters path else if (lowerCasepath.length() > 2) { // handling more then two characters path final int key = getIntKey(lowerCasepath); if (category == IElementsIndex.ANY_CETEGORY) { firstTwoCharsToEntries.forEachValue(new TObjectProcedure<TIntObjectHashMap<Object>>() { public boolean execute(TIntObjectHashMap<Object> map) { Object objResult = map.get(key); if (objResult != null) { addObjRefToListWithPathCheck(toReturn, objResult, lowerCasepath); } return true; } }); } else { TIntObjectHashMap<Object> map = firstTwoCharsToEntries.get(category); if (map != null) { Object objResult = map.get(key); if (objResult != null) { addObjRefToListWithPathCheck(toReturn, objResult, lowerCasepath); } } } } if (filterByNamespace) { if (namespace != null) { List<IElementEntry> filter = new ArrayList<IElementEntry>(); for (IElementEntry e : toReturn) { Object value = e.getValue(); if (value instanceof AbstractPHPEntryValue) { AbstractPHPEntryValue m = (AbstractPHPEntryValue) value; String nameSpace2 = m.getNameSpace(); if (nameSpace2 != null && nameSpace2.equals(namespace)) { filter.add(e); } } } return filter; } else { List<IElementEntry> filter = new ArrayList<IElementEntry>(); for (IElementEntry e : toReturn) { Object value = e.getValue(); if (value instanceof AbstractPHPEntryValue) { AbstractPHPEntryValue m = (AbstractPHPEntryValue) value; if (m instanceof NamespacePHPEntryValue) { filter.add(e); } else { String nameSpace2 = m.getNameSpace(); if (nameSpace2 == null || nameSpace2.length() == 0) { filter.add(e); } } } } return filter; } } return toReturn; } /** * Adds an object reference (that might be an entry or a list of entries) to the specified list. * * @param list * - list to add entries to. * @param reference * - reference. */ @SuppressWarnings("unchecked") private void addObjRefToList(List<IElementEntry> list, Object reference) { if (reference instanceof IElementEntry) { list.add((IElementEntry) reference); } else if (reference instanceof Collection) { list.addAll((Collection<IElementEntry>) reference); } } /** * Adds an object reference (that might be an entry or a list of entries) to the specified list if its lower-case * path starts with the path specified. * * @param list * - list to add entries to. * @param reference * - reference. * @param lowerCasePath * - lower-case path to compare to */ @SuppressWarnings("unchecked") private void addObjRefToListWithPathCheck(List<IElementEntry> list, Object reference, String lowerCasePath) { if (reference instanceof IElementEntry) { if (((IElementEntry) reference).getLowerCaseEntryPath().startsWith(lowerCasePath)) { list.add((IElementEntry) reference); } } else if (reference instanceof Collection<?>) { for (IElementEntry currentEntry : (Collection<IElementEntry>) reference) { if (currentEntry.getLowerCaseEntryPath().startsWith(lowerCasePath)) { list.add(currentEntry); } } } } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public synchronized List<IElementEntry> getEntries(int category, String path) { int indexOf = path.lastIndexOf('\\'); boolean filterByNamespace = ContentAssistUtils.isFilterByNamespace(); String namespace = null; if (indexOf != -1) { namespace = path.substring(0, indexOf); path = path.substring(indexOf + 1); } final String lowerCasePath = path.toLowerCase(); if (category == IElementsIndex.ANY_CETEGORY) { final List<IElementEntry> result = new ArrayList<IElementEntry>(); pathToEntries.forEachValue(new TObjectProcedure<THashMap<String, Object>>() { public boolean execute(THashMap<String, Object> map) { Object resultObject = map.get(lowerCasePath); if (resultObject == null) { return true; } if (resultObject instanceof Collection) { result.addAll((Collection<IElementEntry>) resultObject); } else if (resultObject instanceof IElementEntry) { result.add((IElementEntry) resultObject); } return true; } }); if (filterByNamespace && namespace != null) { List<IElementEntry> filter = new ArrayList<IElementEntry>(); for (IElementEntry e : result) { Object value = e.getValue(); if (value instanceof AbstractPHPEntryValue) { AbstractPHPEntryValue m = (AbstractPHPEntryValue) value; String nameSpace2 = m.getNameSpace(); if (nameSpace2 != null && nameSpace2.equals(namespace)) { filter.add(e); } } } return filter; } return result; } else { Map<String, Object> map = pathToEntries.get(category); if (map == null) { return Collections.emptyList(); } final List<IElementEntry> result = new ArrayList<IElementEntry>(); Object resultObject = map.get(lowerCasePath); if (resultObject == null) { return Collections.emptyList(); } if (resultObject instanceof Collection) { result.addAll((Collection<IElementEntry>) resultObject); } else if (resultObject instanceof IElementEntry) { result.add((IElementEntry) resultObject); } if (filterByNamespace && namespace != null) { List<IElementEntry> filter = new ArrayList<IElementEntry>(); for (IElementEntry e : result) { Object value = e.getValue(); if (value instanceof AbstractPHPEntryValue) { AbstractPHPEntryValue m = (AbstractPHPEntryValue) value; String nameSpace2 = m.getNameSpace(); if (nameSpace2 != null && nameSpace2.equals(namespace)) { filter.add(e); } } } return filter; } return result; } } /** * {@inheritDoc} */ public synchronized int size() { int size = 0; for (List<UnpackedEntry> moduleEntries : entries.values()) { size += moduleEntries.size(); } return size; } /** * {@inheritDoc} */ public synchronized List<IElementEntry> getModuleEntries(IModule module) { List<IElementEntry> result = new ArrayList<IElementEntry>(); List<UnpackedEntry> moduleEntries = entries.get(module); if (moduleEntries != null) { result.addAll(moduleEntries); } return result; } /** * {@inheritDoc} */ public synchronized Set<IModule> getModules() { return Collections.unmodifiableSet(entries.keySet()); } public IModule[] getAllModules() { return timeStamps.keys(new IModule[timeStamps.size()]); } /** * Removes all the entries that have the same path and module as the entry specified from the * firstTowCharsToEntries. * * @param entryToRemove * - entry to remove. */ @SuppressWarnings("unchecked") private void removeEntriesFromFirstTwoCharcToEntries(UnpackedEntry entryToRemove) { TIntObjectHashMap<Object> map = firstTwoCharsToEntries.get(entryToRemove.getCategory()); if (map == null) { return; } // skipping those entries having not enough characters in path String lowerCasePath = entryToRemove.getLowerCaseEntryPath(); if (lowerCasePath.length() < 2) { return; } int key = getIntKey(lowerCasePath); Object pathToEntriesValue = map.get(key); if (pathToEntriesValue == null) { return; } if (pathToEntriesValue instanceof UnpackedEntry) { if (entryToRemove.equals(pathToEntriesValue)) { map.remove(key); } } else if (pathToEntriesValue instanceof Collection) { // removing all entries that have the same module as the entry // specified Iterator<IElementEntry> it = ((Collection<IElementEntry>) pathToEntriesValue).iterator(); while (it.hasNext()) { IElementEntry currentEntry = it.next(); if (lowerCasePath.equals(currentEntry.getLowerCaseEntryPath()) && entryToRemove.getModule().equals(currentEntry.getModule())) { it.remove(); } } if (((Collection<IElementEntry>) pathToEntriesValue).size() == 0) { map.remove(key); } } } /** * Gets integter key that encodes first two characters of the path. * * @param lowerCasePath * - lower-case path * @return integer key */ private int getIntKey(String lowerCasePath) { short firstCharacter = (short) lowerCasePath.charAt(0); short secondCharacter = (short) lowerCasePath.charAt(1); converter.clear(); converter.putShort(firstCharacter); converter.putShort(secondCharacter); converter.flip(); int key = converter.getInt(); return key; } /** * Removes all the entries that have the same path and module as the entry specified from the firstCharToEntries. * * @param entryToRemove * - entry to remove. */ @SuppressWarnings("unchecked") private void removeEntriesFromFirstCharToEntries(UnpackedEntry entryToRemove) { TShortObjectHashMap<Object> map = firstCharToEntries.get(entryToRemove.getCategory()); if (map == null) { return; } // skipping those entries having not enough characters in path String lowerCasePath = entryToRemove.getLowerCaseEntryPath(); if (lowerCasePath.length() == 0) { return; } short firstCharacter = (short) lowerCasePath.charAt(0); Object pathToEntriesValue = map.get(firstCharacter); if (pathToEntriesValue == null) { return; } if (pathToEntriesValue instanceof UnpackedEntry) { if (entryToRemove.equals(pathToEntriesValue)) { map.remove(firstCharacter); } } else if (pathToEntriesValue instanceof Collection) { Iterator<IElementEntry> it = ((Collection<IElementEntry>) pathToEntriesValue).iterator(); // removing all entries that have the same module as the entry // specified while (it.hasNext()) { IElementEntry currentEntry = it.next(); if (lowerCasePath.equals(currentEntry.getLowerCaseEntryPath()) && entryToRemove.getModule().equals(currentEntry.getModule())) { it.remove(); } } if (((Collection<IElementEntry>) pathToEntriesValue).size() == 0) { map.remove(firstCharacter); } } } /** * Removes all the entries that have the same path and module as the entry specified from the pathToEntries. * * @param entryToRemove * - entry to remove. */ @SuppressWarnings("unchecked") private void removeEntriesFromPathToEntries(UnpackedEntry entryToRemove) { Map<String, Object> map = pathToEntries.get(entryToRemove.getCategory()); if (map == null) { return; } Object pathToEntriesValue = map.get(entryToRemove.getLowerCaseEntryPath()); if (pathToEntriesValue == null) { return; } if (pathToEntriesValue instanceof UnpackedEntry) { if (entryToRemove.equals(pathToEntriesValue)) { map.remove(entryToRemove.getLowerCaseEntryPath()); } } else if (pathToEntriesValue instanceof Collection) { // removing all entries that have the same module as the entry // specified Iterator<IElementEntry> it = ((Collection<IElementEntry>) pathToEntriesValue).iterator(); while (it.hasNext()) { IElementEntry currentEntry = it.next(); if (entryToRemove.getModule().equals(currentEntry.getModule())) { it.remove(); } } if (((Collection<IElementEntry>) pathToEntriesValue).size() == 0) { map.remove(entryToRemove.getLowerCaseEntryPath()); } } } /** * Adds entry to first character of the path -> entries map. * * @param entry * - entry. */ @SuppressWarnings("unchecked") private void addEntryToFirstTwoCharsToEntriesMap(UnpackedEntry entry) { TIntObjectHashMap<Object> map = firstTwoCharsToEntries.get(entry.getCategory()); if (map == null) { map = new TIntObjectHashMap<Object>(); firstTwoCharsToEntries.put(entry.getCategory(), map); } // skipping those entries having not enough characters in path String lowerCasePath = entry.getLowerCaseEntryPath(); if (lowerCasePath.length() < 2) { return; } int key = getIntKey(lowerCasePath); Object objectResult = map.get(key); if (objectResult == null) { map.put(key, entry); } else { if (objectResult instanceof IElementEntry) { if (!objectResult.equals(entry)) { IElementEntry oldEntry = (IElementEntry) objectResult; Set<IElementEntry> list = new HashSet<IElementEntry>(2); list.add(oldEntry); list.add(entry); map.put(key, list); } } else if (objectResult instanceof Collection<?>) { if (!((HashSet<IElementEntry>) objectResult).contains(entry)) { ((HashSet<IElementEntry>) objectResult).add(entry); } } } } /** * Adds entry to first character of the path -> entries map. * * @param entry * - entry. */ @SuppressWarnings("unchecked") private void addEntryToFirstCharToEntriesMap(UnpackedEntry entry) { TShortObjectHashMap<Object> map = firstCharToEntries.get(entry.getCategory()); if (map == null) { map = new TShortObjectHashMap<Object>(); firstCharToEntries.put(entry.getCategory(), map); } // skipping those entries having not enough characters in path String lowerCasePath = entry.getLowerCaseEntryPath(); if (lowerCasePath.length() == 0) { return; } short firstCharacter = (short) lowerCasePath.charAt(0); Object objectResult = map.get(firstCharacter); if (objectResult == null) { map.put(firstCharacter, entry); } else { if (objectResult instanceof IElementEntry) { if (!objectResult.equals(entry)) { IElementEntry oldEntry = (IElementEntry) objectResult; Set<IElementEntry> list = new HashSet<IElementEntry>(2); list.add(oldEntry); list.add(entry); map.put(firstCharacter, list); } } else if (objectResult instanceof Collection<?>) { if (!((Collection<IElementEntry>) objectResult).contains(entry)) { ((Collection<IElementEntry>) objectResult).add(entry); } } } } /** * Adds entry to path->entries map. * * @param entry * - entry. */ @SuppressWarnings("unchecked") private void addEntryToPathToEntriesMap(UnpackedEntry entry) { int category = entry.getCategory(); String entryPathLowerCase = entry.getLowerCaseEntryPath(); // getting path->entries map, creating the new one if needed THashMap<String, Object> map = pathToEntries.get(category); if (map == null) { map = new THashMap<String, Object>(); pathToEntries.put(category, map); } Object pathToEntriesValue = map.get(entryPathLowerCase); if (pathToEntriesValue == null) { map.put(entryPathLowerCase, entry); } else { if (pathToEntriesValue instanceof UnpackedEntry) { if (entry.equals(pathToEntriesValue)) { return; } Set<UnpackedEntry> val = new HashSet<UnpackedEntry>(2); val.add((UnpackedEntry) pathToEntriesValue); val.add(entry); map.put(entryPathLowerCase, val); } else if (pathToEntriesValue instanceof Collection) { Collection<UnpackedEntry> val = (Collection<UnpackedEntry>) pathToEntriesValue; if (val.contains(entry)) { return; } val.add(entry); } } } /** * Adds entry to the list of module entries. * * @param module * - module. * @param entry * - entry. */ private void addEntryToModuleList(IModule module, UnpackedEntry entry) { List<UnpackedEntry> moduleEntries = entries.get(module); if (moduleEntries == null) { moduleEntries = new ArrayList<UnpackedEntry>(); entries.put(module, moduleEntries); } moduleEntries.add(entry); } public void removeTimeStamp(IModule module) { timeStamps.remove(module); } }