/*******************************************************************************
* Copyright (c) 2011, 2014 Andrew Gvozdev 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
*
* Contributors:
* Andrew Gvozdev - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.core.language.settings.providers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;
import org.eclipse.cdt.internal.core.WeakHashSet;
import org.eclipse.cdt.internal.core.WeakHashSetSynchronized;
/**
* The class representing the (in-memory) storage for language settings entries {@link ICLanguageSettingEntry}.
*
* @since 5.4
*/
public class LanguageSettingsStorage implements Cloneable {
/** Storage to keep settings entries. */
protected Map<String, // languageId
Map<String, // resource project path
List<ICLanguageSettingEntry>>> fStorage = new HashMap<String, Map<String, List<ICLanguageSettingEntry>>>();
/**
* Pool of LSE lists implemented as WeakHashSet. That allows to gain memory savings
* at the expense of CPU time. WeakHashSet handles garbage collection when a list is not
* referenced anywhere else. See JavaDoc {@link java.lang.ref.WeakReference} about weak reference objects.
*/
private static WeakHashSet<List<ICLanguageSettingEntry>> listPool = new WeakHashSetSynchronized<List<ICLanguageSettingEntry>>();
/**
* Returns the list of setting entries for the given resource and language.
* <br> Note that this list is <b>unmodifiable</b>.
*
* @param rcProjectPath - path to the resource relative to the project.
* @param languageId - language id.
*
* @return the list of setting entries or {@code null} if no settings defined.
*/
public List<ICLanguageSettingEntry> getSettingEntries(String rcProjectPath, String languageId) {
List<ICLanguageSettingEntry> entries = null;
Map<String, List<ICLanguageSettingEntry>> langMap = fStorage.get(languageId);
if (langMap!=null) {
entries = langMap.get(rcProjectPath);
}
return entries;
}
/**
* Some providers may collect entries in pretty much random order. For the intent of
* predictability, UI usability and efficient storage the entries are sorted by kinds
* and secondary by name for kinds where the secondary order is not significant.
*
* @param entries - list of entries to sort.
* @return - sorted entries.
*/
private List<ICLanguageSettingEntry> sortEntries(List<? extends ICLanguageSettingEntry> entries) {
List<ICLanguageSettingEntry> sortedEntries = new ArrayList<>(entries);
Collections.sort(sortedEntries, new Comparator<ICLanguageSettingEntry>() {
/**
* This comparator sorts by kinds first and the macros are sorted additionally by name.
*/
@Override
public int compare(ICLanguageSettingEntry entry0, ICLanguageSettingEntry entry1) {
int kind0 = entry0.getKind();
int kind1 = entry1.getKind();
if (kind0 == ICSettingEntry.MACRO && kind1 == ICSettingEntry.MACRO) {
return entry0.getName().compareTo(entry1.getName());
}
return kind0 - kind1;
}});
return sortedEntries;
}
/**
* Sets language settings entries for the resource and language.
*
* @param rcProjectPath - path to the resource relative to the project. If {@code null} the entries are
* considered to be being defined as default entries for resources.
* @param languageId - language id. If {@code null}, then entries are considered
* to be defined for the language scope.
* @param entries - language settings entries to set.
*/
public void setSettingEntries(String rcProjectPath, String languageId,
List<? extends ICLanguageSettingEntry> entries) {
synchronized (fStorage) {
if (entries!=null) {
Map<String, List<ICLanguageSettingEntry>> langMap = fStorage.get(languageId);
if (langMap == null) {
langMap = new HashMap<String, List<ICLanguageSettingEntry>>();
fStorage.put(languageId, langMap);
}
List<ICLanguageSettingEntry> sortedEntries = getPooledList(sortEntries(entries), false);
langMap.put(rcProjectPath, sortedEntries);
} else {
// reduct the empty maps in the tables
Map<String, List<ICLanguageSettingEntry>> langMap = fStorage.get(languageId);
if (langMap != null) {
langMap.remove(rcProjectPath);
if (langMap.isEmpty()) {
fStorage.remove(languageId);
}
}
}
}
}
/**
* @return {@code true} if the storage is empty or {@code false} otherwise.
*/
public boolean isEmpty() {
return fStorage.isEmpty();
}
/**
* Clear all the entries for all resources and all languages.
*/
public void clear() {
synchronized (fStorage) {
fStorage.clear();
}
}
/**
* @return set of all languages associated with the entries.
* Note that the storage can keep default entries for the language scope
* of the provider, so the set can contain {@code null}.
*/
public Set<String> getLanguages() {
return new HashSet<String>(fStorage.keySet());
}
/**
* Returns set of paths for all resources associated with entries for given language.
* The paths are project relative.
*
* @param languageId - language ID.
* @return the set of resource paths associated with entries for the given language or empty set.
* Note that the storage can keep default entries for resources, so the set can contain {@code null}.
*/
public Set<String> getResourcePaths(String languageId) {
Map<String, List<ICLanguageSettingEntry>> rcPathsMap = fStorage.get(languageId);
if (rcPathsMap == null) {
return new HashSet<String>();
}
return new HashSet<String>(rcPathsMap.keySet());
}
/**
* Find and return the equal list of entries from the pool.
*
* @param entries - list of entries to pool.
* @param copy - specify {@code true} to copy the list in order to prevent
* back-door modification on the original list changes.
* @return returns the list of entries from the pool.
*/
private static List<ICLanguageSettingEntry> getPooledList(List<ICLanguageSettingEntry> entries, boolean copy) {
if (entries == null)
return null;
List<ICLanguageSettingEntry> pooledList = listPool.get(entries);
if (pooledList != null) {
return pooledList;
}
if (entries.size() == 0) {
return getPooledEmptyList();
}
if (copy) {
entries = new ArrayList<ICLanguageSettingEntry>(entries);
}
pooledList = Collections.unmodifiableList(entries);
return listPool.add(pooledList);
}
/**
* Find and return the equal list of entries from the pool to conserve the memory.
*
* @param entries - list of entries to pool.
* @return returns the list of entries from the pool.
*/
public static List<ICLanguageSettingEntry> getPooledList(List<ICLanguageSettingEntry> entries) {
return getPooledList(entries, true);
}
/**
* @return Returns the empty immutable list which is pooled. Use this call rather than creating
* new empty array to ensure that faster shallow operator '==' can be used instead of equals()
* which goes deep on HashMaps.
*/
public static List<ICLanguageSettingEntry> getPooledEmptyList() {
List<ICLanguageSettingEntry> pooledEmptyList = Collections.emptyList();
return listPool.add(pooledEmptyList);
}
/**
* Clone storage for the entries. Copies references for lists of entries as a whole.
* Note that that is OK as the lists kept in storage are unmodifiable and pooled.
*/
@Override
public LanguageSettingsStorage clone() throws CloneNotSupportedException {
LanguageSettingsStorage storageClone = (LanguageSettingsStorage) super.clone();
storageClone.fStorage = new HashMap<String, Map<String, List<ICLanguageSettingEntry>>>();
synchronized (fStorage) {
Set<Entry<String, Map<String, List<ICLanguageSettingEntry>>>> entrySetLang = fStorage.entrySet();
for (Entry<String, Map<String, List<ICLanguageSettingEntry>>> entryLang : entrySetLang) {
String langId = entryLang.getKey();
Map<String, List<ICLanguageSettingEntry>> mapRc = entryLang.getValue();
Map<String, List<ICLanguageSettingEntry>> mapRcClone = new HashMap<String, List<ICLanguageSettingEntry>>();
Set<Entry<String, List<ICLanguageSettingEntry>>> entrySetRc = mapRc.entrySet();
for (Entry<String, List<ICLanguageSettingEntry>> entryRc : entrySetRc) {
String rcProjectPath = entryRc.getKey();
List<ICLanguageSettingEntry> lsEntries = entryRc.getValue();
// don't need to clone entries, they are from the LSE lists pool
mapRcClone.put(rcProjectPath, lsEntries);
}
storageClone.fStorage.put(langId, mapRcClone);
}
}
return storageClone;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fStorage == null) ? 0 : fStorage.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LanguageSettingsStorage other = (LanguageSettingsStorage) obj;
if (fStorage == null) {
if (other.fStorage != null)
return false;
} else if (!fStorage.equals(other.fStorage))
return false;
return true;
}
}