package com.intellij.flex.uiDesigner.libraries;
import com.intellij.flex.uiDesigner.abc.AbcTranscoder;
import com.intellij.flex.uiDesigner.abc.Decoder;
import com.intellij.flex.uiDesigner.abc.DecoderException;
import com.intellij.flex.uiDesigner.abc.Encoder;
import com.intellij.flex.uiDesigner.io.IOUtil;
import com.intellij.openapi.util.Condition;
import gnu.trove.THashMap;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import static com.intellij.flex.uiDesigner.libraries.Definition.ResolvedState;
public class LibrarySorter {
@Nullable
private final DefinitionProcessor definitionProcessor;
@Nullable
private final DefinitionMapProcessor definitionMapProcessor;
public LibrarySorter() {
this(null, null);
}
public LibrarySorter(@Nullable DefinitionProcessor definitionProcessor, @Nullable DefinitionMapProcessor definitionMapProcessor) {
this.definitionProcessor = definitionProcessor;
this.definitionMapProcessor = definitionMapProcessor;
}
private static List<LibrarySetItem> collectItems(final List<Library> libraries, Map<CharSequence, Definition> definitionMap,
Condition<String> isExternal) throws IOException {
final List<LibrarySetItem> items = new ArrayList<>(libraries.size());
final CatalogXmlBuilder catalogXmlBuilder = new CatalogXmlBuilder(definitionMap, isExternal);
for (Library library : libraries) {
LibrarySetItem item = new LibrarySetItem(library);
catalogXmlBuilder.setLibrary(item);
IOUtil.parseXml(library.getCatalogFile(), catalogXmlBuilder);
if (item.hasDefinitions() || library.hasResourceBundles()) {
items.add(item);
}
}
return items;
}
public SortResult sort(List<Library> libraries, File outFile, Condition<String> isExternal, boolean returnDefinitionMap) throws IOException {
final THashMap<CharSequence, Definition> definitionMap = new THashMap<>(libraries.size() * 128, AbcTranscoder.HASHING_STRATEGY);
final List<LibrarySetItem> unsortedItems = collectItems(libraries, definitionMap, isExternal);
final AbcMerger abcMerger = new AbcMerger(definitionMap, outFile, definitionProcessor);
try {
final ArrayList<Library> resourceOrStyleHolders = new ArrayList<>(unsortedItems.size());
for (LibrarySetItem item : unsortedItems) {
if (!item.hasDefinitions()) {
if (item.library.hasResourceBundles()) {
resourceOrStyleHolders.add(item.library);
}
continue;
}
if (item.library.hasResourceBundles() || item.library.isStyleOwner()) {
resourceOrStyleHolders.add(item.library);
}
abcMerger.process(item.library);
}
if (definitionMapProcessor != null) {
definitionMapProcessor.process(definitionMap, abcMerger);
}
final List<Decoder> decoders = new ArrayList<>(definitionMap.size());
final String[] singleStringArray = new String[1];
definitionMap.forEachValue(definition -> {
if (definition.doAbcData != null &&
(definition.resolved == ResolvedState.YES || (definition.resolved == ResolvedState.UNKNOWN &&
processDependencies(decoders, definition, definitionMap, singleStringArray)))) {
decoders.add(createDecoder(definition));
}
return true;
});
abcMerger.end(decoders, new Encoder());
return new SortResult(returnDefinitionMap ? definitionMap : null, resourceOrStyleHolders);
}
finally {
abcMerger.close();
}
}
private static Decoder createDecoder(Definition definition) {
final Decoder decoder = new Decoder(definition.doAbcData, definition.doAbcData.abcModifier);
definition.doAbcData = null;
return decoder;
}
@SuppressWarnings({"UnusedDeclaration"})
@TestOnly
private static Map<CharSequence, Definition> getDefinitions(LibrarySetItem library, THashMap<CharSequence, Definition> definitionMap) {
Map<CharSequence, Definition> definitions = new HashMap<>();
for (Entry<CharSequence, Definition> entry : definitionMap.entrySet()) {
if (entry.getValue().getLibrary() == library) {
definitions.put(entry.getKey(), entry.getValue());
}
}
return definitions;
}
private static boolean processDependencies(List<Decoder> decoders, Definition definition,
Map<CharSequence, Definition> definitionMap, String[] singleStringArray) throws DecoderException {
// set before to prevent stack overflow for crossed dependencies
definition.resolved = ResolvedState.YES;
final String[] dependencies;
if (definition.dependency == null) {
dependencies = definition.dependencies;
}
else {
dependencies = singleStringArray;
dependencies[0] = definition.dependency;
}
for (String dependencyId : dependencies) {
final Definition dependency = definitionMap.get(dependencyId);
if (dependency == null || dependency.resolved == ResolvedState.NO ||
(dependency.resolved == ResolvedState.UNKNOWN && !processDependencies(decoders, dependency, definitionMap, singleStringArray))) {
definition.markAsUnresolved();
//System.out.print("Mark " + definition.name + " as unresolved due to missed " + dependencyId + "\n");
definition.resolved = ResolvedState.NO;
return false;
}
if (dependency.doAbcData != null) {
decoders.add(createDecoder(dependency));
}
}
return true;
}
static class SortResult {
final @Nullable THashMap<CharSequence, Definition> definitionMap;
final List<Library> libraries;
// only if restored from cache
final String[] libraryPaths;
int id;
SortResult(@Nullable THashMap<CharSequence, Definition> definitionMap, List<Library> libraries) {
this.definitionMap = definitionMap;
this.libraries = libraries;
libraryPaths = null;
}
SortResult(@Nullable THashMap<CharSequence, Definition> definitionMap, String[] libraryPaths) {
this.definitionMap = definitionMap;
libraries = null;
this.libraryPaths = libraryPaths;
}
}
}