/*
* Copyright 2003-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.languageScope;
import jetbrains.mps.classloading.ClassLoaderManager;
import jetbrains.mps.classloading.DeployListener;
import jetbrains.mps.components.CoreComponent;
import jetbrains.mps.module.ReloadableModule;
import jetbrains.mps.smodel.SLanguageHierarchy;
import jetbrains.mps.util.SimpleLRUCache;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SLanguage;
import org.jetbrains.mps.openapi.util.ProgressMonitor;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* User: fyodor
* Date: 8/27/12
*/
public class LanguageScopeFactory implements CoreComponent, DeployListener {
private static final Logger LOG = Logger.getLogger(LanguageScopeFactory.class);
public static final int CACHE_SIZE = 1000;
private static LanguageScopeFactory INSTANCE;
private ClassLoaderManager myLoaderManager;
public static LanguageScopeFactory getInstance () {
return INSTANCE;
}
private ConcurrentHashMap<String, Integer> myNamespaceIndices = new ConcurrentHashMap<String, Integer>();
private AtomicInteger myBits = new AtomicInteger(0);
private SimpleLRUCache<LanguagesHolder> myCachedLanguages;
public LanguageScopeFactory(ClassLoaderManager loaderManager) {
myLoaderManager = loaderManager;
initCache();
}
@Override
public void init() {
if (INSTANCE != null) {
throw new IllegalStateException("double initialization");
}
INSTANCE = this;
myLoaderManager.addListener(this);
}
@Override
public void dispose() {
myCachedLanguages = null;
myNamespaceIndices.clear();
myLoaderManager.removeListener(this);
INSTANCE = null;
}
@Override
public void onUnloaded(Set<ReloadableModule> unloadedModules, @NotNull ProgressMonitor monitor) {
initCache();
}
@Override
public void onLoaded(Set<ReloadableModule> loadedModules, @NotNull ProgressMonitor monitor) {
// do nothing
}
private void initCache() {
myCachedLanguages = new SimpleLRUCache<LanguagesHolder>(CACHE_SIZE) {
@Override
protected void purged(LanguagesHolder holder) {
holder.clear();
}
};
}
public int getIndexOf(String namespace) {
if (!myNamespaceIndices.containsKey(namespace)) {
myNamespaceIndices.putIfAbsent(namespace, myBits.getAndIncrement());
}
return myNamespaceIndices.get(namespace);
}
/**
* Produces a new <code>LanguageScope</code> from the two corresponding to the parameters <code>langs1</code> and
* <code>langs2</code> by merging.
* @param langs1
* @param langs2
* @return
*/
public LanguageScope getLanguageScope (Collection<SLanguage> langs1, Collection<SLanguage> langs2) {
LanguageScope langScope1 = getLanguageScope(langs1);
LanguageScope langScope2 = getLanguageScope(langs2);
return langScope1.disjunction(langScope2);
}
/**
* Produces a new <code>LanguageScope</code> from the ones corresponding to the collections in parameter <code>multiLangs</code>.
* @param multiLangs
* @return
*/
public LanguageScope getMultiLanguageScope (Iterable<? extends Collection<SLanguage>> multiLangs) {
LanguageScope langScope = null;
for(Collection<SLanguage> langs: multiLangs) {
LanguageScope tmp = getLanguageScope(langs);
langScope = langScope == null? tmp : langScope.disjunction(tmp);
}
return langScope;
}
/**
* This method implements a memoization scheme for the "extended languages hierarchy" using the specified collection of languages.
* The objects constituting the contents of the <code>langs</code> collection are expected to never change, and thus can be treated as opaque.
* As a result, we can cache the calculated scope value with the key derived from objects representing the languages.
* @param langs the dependencies collection; all languages included in this scope
* @return
*/
public LanguageScope getLanguageScope (Collection<SLanguage> langs) {
LanguagesHolder cached = getHolder(langs);
if (cached.hasScope()) {
return cached.getScope();
}
BitSet nsBitSet = new BitSet(myBits.intValue());
for (SLanguage lng: new SLanguageHierarchy(langs).getExtended()) {
updateNamespaceBit(nsBitSet, lng.getQualifiedName());
}
LanguageScope langScope = new LanguageScope(this, nsBitSet);
cached.setScope(langScope);
return langScope;
}
private void updateNamespaceBit(BitSet nsBitSet, String namespace) {
if (myNamespaceIndices.containsKey(namespace)) {
nsBitSet.set(myNamespaceIndices.get(namespace));
}
else {
myNamespaceIndices.putIfAbsent(namespace, myBits.getAndIncrement());
nsBitSet.set(myNamespaceIndices.get(namespace));
}
}
private LanguagesHolder getHolder(Collection<SLanguage> langs) {
return myCachedLanguages.cacheObject(new LanguagesHolder(new ArrayList<SLanguage>(langs)));
}
private static class LanguagesHolder {
private SortedIdentityList<SLanguage> myWrappers;
private LanguageScope myLangScope = null;
public LanguagesHolder(Collection<SLanguage> langs) {
myWrappers = new SortedIdentityList<>(langs);
}
public boolean hasScope () {
return myLangScope != null;
}
public void clear () {
this.myLangScope = null;
}
public void setScope (LanguageScope langScope) {
this.myLangScope = langScope;
}
public LanguageScope getScope () {
return myLangScope;
}
@Override
public boolean equals(Object that) {
if (that == this) {
return true;
}
return that instanceof LanguagesHolder && this.myWrappers.equals(((LanguagesHolder) that).myWrappers);
}
@Override
public int hashCode() {
return myWrappers.hashCode()*17 + 31;
}
}
private static class SortedIdentityList<T> extends AbstractList<IdentityWrapper<T>> {
private ArrayList<IdentityWrapper<T>> myList;
public SortedIdentityList(Collection<T> original) {
this.myList = new ArrayList<>(original.size());
for (T o: original) {
myList.add(new IdentityWrapper<T>(o));
}
Collections.sort(myList);
}
@Override
public IdentityWrapper<T> get(int index) {
return myList.get(index);
}
@Override
public int size() {
return myList.size();
}
}
private static class IdentityWrapper<K> implements Comparable<IdentityWrapper<K>> {
private final K myObject;
private final int myHash;
public IdentityWrapper(K k) {
myObject = k;
myHash = k != null ? System.identityHashCode(myObject) : 113;
}
public K unwrap () {
return myObject;
}
@Override
public int compareTo(IdentityWrapper<K> that) {
return this.myHash - that.myHash;
}
@Override
public int hashCode() {
return myHash;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object that) {
if (that == this) {
return true;
}
return that instanceof IdentityWrapper && this.myObject == ((IdentityWrapper<K>) that).myObject;
}
}
}