/*
* Copyright 2003-2014 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.persistence.registry;
import jetbrains.mps.smodel.adapter.ids.MetaIdHelper;
import jetbrains.mps.smodel.adapter.ids.SConceptId;
import jetbrains.mps.smodel.adapter.ids.SContainmentLinkId;
import jetbrains.mps.smodel.adapter.ids.SLanguageId;
import jetbrains.mps.smodel.adapter.ids.SPropertyId;
import jetbrains.mps.smodel.adapter.ids.SReferenceLinkId;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* Fraction of a structure model, sufficient to serialize/de-serialize given model/nodes.
*
* No thread safety is ensured, deemed to be populated from a single thread.
* @author Artem Tikhomirov
*/
public class IdInfoRegistry {
private final HashMap<SLanguageId, LangInfo> myLanguagesInUse;
/*
* In fact, just a view of myLanguagesInUse.values().getConceptsInUse().select(it -> map(it.getConceptId(), it))
*/
private final HashMap<SConceptId, ConceptInfo> myRegistry;
public IdInfoRegistry() {
myRegistry = new HashMap<SConceptId, ConceptInfo>();
myLanguagesInUse = new HashMap<SLanguageId, LangInfo>();
}
/**
* @return ordered set of languages known to this registry
*/
public List<LangInfo> getLanguagesInUse() {
ArrayList<LangInfo> rv = new ArrayList<LangInfo>(myLanguagesInUse.values());
Collections.sort(rv);
return rv;
}
/**
* Records a language (unless already known) in the collector
* @return utility object that keeps language information essential for persistence
*/
@NotNull
public LangInfo registerLanguage(@NotNull SLanguageId lang, @NotNull String languageName) {
LangInfo langInfo = myLanguagesInUse.get(lang);
if (langInfo == null) {
myLanguagesInUse.put(lang, langInfo = new LangInfo(lang, languageName));
}
return langInfo;
}
/**
* Records a concept (unless already known) in the collector.
* NOTE, concept's language has to be registered beforehand.
* @return utility object that keeps concept information essential for persistence
* @throws java.lang.IllegalArgumentException if concept to register comes from unknown language
*/
@NotNull
public ConceptInfo registerConcept(@NotNull SConceptId concept, @NotNull String conceptName) throws IllegalArgumentException {
if (!knows(concept.getLanguageId())) {
throw new IllegalArgumentException(String.format("Concept %s comes from a language not yet registered here", concept));
}
ConceptInfo ci = myRegistry.get(concept);
if (ci != null) {
return ci;
}
LangInfo li = myLanguagesInUse.get(concept.getLanguageId());
ci = new ConceptInfo(concept, conceptName);
li.register(ci);
myRegistry.put(concept, ci);
return ci;
}
public boolean knows(@NotNull SLanguageId lang) {
return myLanguagesInUse.containsKey(lang);
}
public boolean knows(@NotNull SConceptId concept) {
return myRegistry.containsKey(concept);
}
@Nullable
public ConceptInfo get(@NotNull SConceptId concept) {
return myRegistry.get(concept);
}
public ConceptInfo find(@NotNull SConcept concept) {
final SConceptId id = MetaIdHelper.getConcept(concept);
assert id != null; // original ModelWriter9.saveNode assumed this
assert myRegistry.containsKey(id); // the way IdInfoCollector is built shall ensure concept of any node in a model is registered
return myRegistry.get(id);
}
public PropertyInfo find(@NotNull SProperty property) {
SPropertyId id = MetaIdHelper.getProperty(property);
assert id != null;
return myRegistry.get(id.getConceptId()).find(id);
}
public AssociationLinkInfo find(@NotNull SReferenceLink link) {
SReferenceLinkId id = MetaIdHelper.getAssociation(link);
assert id != null;
return myRegistry.get(id.getConceptId()).find(id);
}
public AggregationLinkInfo find(@NotNull SContainmentLink link) {
SContainmentLinkId id = MetaIdHelper.getAggregation(link);
assert id != null;
return myRegistry.get(id.getConceptId()).find(id);
}
/**
* Treat set of known meta-objects as closed and assign string index values to each and every meta-object registered
* @see ConceptInfo#getIndex()
* @param indexEncoder translates internal identity integer value into an index for textual persistence
*/
public void initializeIndexValues(@NotNull IndexEncoder indexEncoder) {
HashSet<String> usedConceptIndexes = new HashSet<String>();
HashSet<String> usedPropertyIndexes = new HashSet<String>();
HashSet<String> usedAssociationIndexes = new HashSet<String>();
HashSet<String> usedAggregationIndexes = new HashSet<String>();
// iterate from language to ensure the same order (and same hash conflict resolution result) for subsequent runs
for (LangInfo langInfo : getLanguagesInUse()) {
for (ConceptInfo ci : langInfo.getConceptsInUse()) {
fill(usedConceptIndexes, ci, indexEncoder);
for (PropertyInfo pi : ci.getPropertiesInUse()) {
fill(usedPropertyIndexes, pi, indexEncoder);
}
for (AssociationLinkInfo li : ci.getAssociationsInUse()) {
fill(usedAssociationIndexes, li, indexEncoder);
}
for (AggregationLinkInfo li : ci.getAggregationsInUse()) {
fill(usedAggregationIndexes, li, indexEncoder);
}
}
}
}
private static void fill(HashSet<String> usedIndexes, BaseInfo bi, IndexEncoder indexEncoder) {
int v = bi.internalKey();
String s;
do {
s = indexEncoder.index(v);
v++;
} while (!usedIndexes.add(s));
bi.setIndex(s);
}
public interface IndexEncoder {
String index(int key);
}
}