From 81225c3cace19ebc7f5adc9433f04f415c6499b7 Mon Sep 17 00:00:00 2001 From: lidongsjtu Date: Thu, 29 Oct 2015 11:03:37 +0800 Subject: [PATCH] KYLIN-1099 Support dictionary of cardinality over 10 millions --- .../org/apache/kylin/dict/DateStrDictionary.java | 2 +- .../org/apache/kylin/dict/DictionaryGenerator.java | 138 ++++++++------------- .../kylin/dict/IDictionaryValueEnumerator.java | 35 ++++++ .../dict/MultipleDictionaryValueEnumerator.java | 83 +++++++++++++ .../kylin/dict/TableColumnValueEnumerator.java | 82 ++++++++++++ .../java/org/apache/kylin/dict/TrieDictionary.java | 4 +- .../apache/kylin/dict/TrieDictionaryBuilder.java | 6 +- .../dict/lookup/ListDictionaryValueEnumerator.java | 50 ++++++++ .../job/hadoop/cube/MergeCuboidMapperTest.java | 5 +- 9 files changed, 309 insertions(+), 96 deletions(-) create mode 100644 dictionary/src/main/java/org/apache/kylin/dict/IDictionaryValueEnumerator.java create mode 100644 dictionary/src/main/java/org/apache/kylin/dict/MultipleDictionaryValueEnumerator.java create mode 100644 dictionary/src/main/java/org/apache/kylin/dict/TableColumnValueEnumerator.java create mode 100644 dictionary/src/main/java/org/apache/kylin/dict/lookup/ListDictionaryValueEnumerator.java diff --git a/dictionary/src/main/java/org/apache/kylin/dict/DateStrDictionary.java b/dictionary/src/main/java/org/apache/kylin/dict/DateStrDictionary.java index 2bf1969..5702be9 100644 --- a/dictionary/src/main/java/org/apache/kylin/dict/DateStrDictionary.java +++ b/dictionary/src/main/java/org/apache/kylin/dict/DateStrDictionary.java @@ -166,7 +166,7 @@ public class DateStrDictionary extends Dictionary { @Override public boolean equals(Object o) { - if ((o instanceof DateStrDictionary) == false) + if (!(o instanceof DateStrDictionary)) return false; DateStrDictionary that = (DateStrDictionary) o; return StringUtils.equals(this.pattern, that.pattern) && this.baseId == that.baseId; diff --git a/dictionary/src/main/java/org/apache/kylin/dict/DictionaryGenerator.java b/dictionary/src/main/java/org/apache/kylin/dict/DictionaryGenerator.java index fefa4f4..6f2c684 100644 --- a/dictionary/src/main/java/org/apache/kylin/dict/DictionaryGenerator.java +++ b/dictionary/src/main/java/org/apache/kylin/dict/DictionaryGenerator.java @@ -21,23 +21,18 @@ package org.apache.kylin.dict; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; +import java.util.*; +import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.apache.kylin.common.KylinConfig; import org.apache.kylin.common.util.Bytes; import org.apache.kylin.common.util.JsonUtil; import org.apache.kylin.dict.lookup.ReadableTable; -import org.apache.kylin.dict.lookup.ReadableTable.TableReader; import org.apache.kylin.metadata.model.DataType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.Lists; - /** * @author yangli9 */ @@ -48,7 +43,7 @@ public class DictionaryGenerator { private static final Logger logger = LoggerFactory.getLogger(DictionaryGenerator.class); - private static final String[] DATE_PATTERNS = new String[] { "yyyy-MM-dd" }; + private static final String[] DATE_PATTERNS = new String[] { "yyyy-MM-dd", "yyyyMMdd" }; private static int getDictionaryMaxCardinality() { try { @@ -58,22 +53,20 @@ public class DictionaryGenerator { } } - public static Dictionary buildDictionaryFromValueList(DictionaryInfo info, List values) { - info.setCardinality(values.size()); - + public static Dictionary buildDictionaryFromValueEnumerator(DictionaryInfo info, IDictionaryValueEnumerator valueEnumerator) throws IOException{ Dictionary dict = null; int baseId = 0; // always 0 for now - int nSamples = 5; - ArrayList samples = new ArrayList(); + final int nSamples = 5; + ArrayList samples = Lists.newArrayListWithCapacity(nSamples); // build dict, case by data type DataType dataType = DataType.getInstance(info.getDataType()); if (dataType.isDateTimeFamily()) - dict = buildDateStrDict(values, baseId, nSamples, samples); + dict = buildDateStrDict(info, valueEnumerator, baseId, nSamples, samples); else if (dataType.isNumberFamily()) - dict = buildNumberDict(values, baseId, nSamples, samples); + dict = buildNumberDict(info, valueEnumerator, baseId, nSamples, samples); else - dict = buildStringDict(values, baseId, nSamples, samples); + dict = buildStringDict(info, valueEnumerator, baseId, nSamples, samples); // log a few samples StringBuilder buf = new StringBuilder(); @@ -83,33 +76,16 @@ public class DictionaryGenerator { buf.append(s.toString()).append("=>").append(dict.getIdFromValue(s)); } logger.info("Dictionary value samples: " + buf.toString()); - logger.info("Dictionary cardinality " + info.getCardinality()); + logger.info("Dictionary cardinality: " + info.getCardinality()); - if (dict instanceof TrieDictionary && values.size() > DICT_MAX_CARDINALITY) - throw new IllegalArgumentException("Too high cardinality is not suitable for dictionary -- " + info.getSourceTable() + "." + info.getSourceColumn() + " cardinality: " + values.size()); + if (dict instanceof TrieDictionary && info.getCardinality() > DICT_MAX_CARDINALITY) + throw new IllegalArgumentException("Too high cardinality is not suitable for dictionary -- " + info.getSourceTable() + "." + info.getSourceColumn() + " cardinality: " + info.getCardinality()); return dict; } - public static Dictionary mergeDictionaries(DictionaryInfo targetInfo, List sourceDicts) { - - HashSet dedup = new HashSet(); - - for (DictionaryInfo info : sourceDicts) { - Dictionary dict = info.getDictionaryObject(); - int minkey = dict.getMinId(); - int maxkey = dict.getMaxId(); - byte[] buffer = new byte[dict.getSizeOfValue()]; - for (int i = minkey; i <= maxkey; ++i) { - int size = dict.getValueBytesFromId(i, buffer, 0); - dedup.add(Bytes.copy(buffer, 0, size)); - } - } - - List valueList = new ArrayList(); - valueList.addAll(dedup); - - return buildDictionaryFromValueList(targetInfo, valueList); + public static Dictionary mergeDictionaries(DictionaryInfo targetInfo, List sourceDicts) throws IOException { + return buildDictionaryFromValueEnumerator(targetInfo, new MultipleDictionaryValueEnumerator(sourceDicts)); } public static Dictionary buildDictionary(DictionaryInfo info, ReadableTable inpTable) throws IOException { @@ -117,30 +93,40 @@ public class DictionaryGenerator { // currently all data types are casted to string to build dictionary // String dataType = info.getDataType(); - logger.info("Building dictionary " + JsonUtil.writeValueAsString(info)); - - ArrayList values = loadColumnValues(inpTable, info.getSourceColumnIndex()); + IDictionaryValueEnumerator columnValueEnumerator = null; + try { + logger.info("Building dictionary " + JsonUtil.writeValueAsString(info)); - return buildDictionaryFromValueList(info, values); + columnValueEnumerator = new TableColumnValueEnumerator(inpTable.getReader(), info.getSourceColumnIndex()); + return buildDictionaryFromValueEnumerator(info, columnValueEnumerator); + } finally { + if (columnValueEnumerator != null) + columnValueEnumerator.close(); + } } - private static Dictionary buildDateStrDict(List values, int baseId, int nSamples, ArrayList samples) { + private static Dictionary buildDateStrDict(DictionaryInfo info, IDictionaryValueEnumerator valueEnumerator, int baseId, int nSamples, ArrayList samples) throws IOException { final int BAD_THRESHOLD = 2; String matchPattern = null; + byte[] value; for (String ptn : DATE_PATTERNS) { matchPattern = ptn; // be optimistic int badCount = 0; + int totalCount = 0; SimpleDateFormat sdf = new SimpleDateFormat(ptn); - for (byte[] value : values) { + + while (valueEnumerator.moveNext()) { + value = valueEnumerator.current(); if (value.length == 0) continue; String str = Bytes.toString(value); try { sdf.parse(str); - if (samples.size() < nSamples && samples.contains(str) == false) + if (samples.size() < nSamples && !samples.contains(str)) samples.add(str); + totalCount++; } catch (ParseException e) { logger.info("Unrecognized datetime value: " + str); badCount++; @@ -150,28 +136,38 @@ public class DictionaryGenerator { } } } - if (matchPattern != null) + if (matchPattern != null) { + info.setCardinality(totalCount); return new DateStrDictionary(matchPattern, baseId); + } } throw new IllegalStateException("Unrecognized datetime value"); } - private static Dictionary buildStringDict(List values, int baseId, int nSamples, ArrayList samples) { + private static Dictionary buildStringDict(DictionaryInfo info, IDictionaryValueEnumerator valueEnumerator, int baseId, int nSamples, ArrayList samples) throws IOException { TrieDictionaryBuilder builder = new TrieDictionaryBuilder(new StringBytesConverter()); - for (byte[] value : values) { + byte[] value; + int totalCount = 0; + while (valueEnumerator.moveNext()) { + value = valueEnumerator.current(); if (value == null) continue; String v = Bytes.toString(value); builder.addValue(v); - if (samples.size() < nSamples && samples.contains(v) == false) + if (samples.size() < nSamples && !samples.contains(v)) samples.add(v); + totalCount++; } + info.setCardinality(totalCount); return builder.build(baseId); } - private static Dictionary buildNumberDict(List values, int baseId, int nSamples, ArrayList samples) { + private static Dictionary buildNumberDict(DictionaryInfo info, IDictionaryValueEnumerator valueEnumerator, int baseId, int nSamples, ArrayList samples) throws IOException { NumberDictionaryBuilder builder = new NumberDictionaryBuilder(new StringBytesConverter()); - for (byte[] value : values) { + byte[] value; + int totalCount = 0; + while (valueEnumerator.moveNext()) { + value = valueEnumerator.current(); if (value == null) continue; String v = Bytes.toString(value); @@ -179,46 +175,12 @@ public class DictionaryGenerator { continue; builder.addValue(v); - if (samples.size() < nSamples && samples.contains(v) == false) + if (samples.size() < nSamples && !samples.contains(v)) samples.add(v); + totalCount++; } + info.setCardinality(totalCount); return builder.build(baseId); } - static ArrayList loadColumnValues(ReadableTable inpTable, int colIndex) throws IOException { - - TableReader reader = inpTable.getReader(); - - try { - ArrayList result = Lists.newArrayList(); - HashSet dedup = new HashSet(); - - while (reader.next()) { - String[] split = reader.getRow(); - - String colValue; - // special single column file, e.g. common_indicator.txt - if (split.length == 1) { - colValue = split[0]; - } - // normal case - else { - if (split.length <= colIndex) { - throw new ArrayIndexOutOfBoundsException("Column no. " + colIndex + " not found, line split is " + Arrays.asList(split)); - } - colValue = split[colIndex]; - } - - if (dedup.contains(colValue) == false) { - dedup.add(colValue); - result.add(Bytes.toBytes(colValue)); - } - } - return result; - - } finally { - reader.close(); - } - } - } diff --git a/dictionary/src/main/java/org/apache/kylin/dict/IDictionaryValueEnumerator.java b/dictionary/src/main/java/org/apache/kylin/dict/IDictionaryValueEnumerator.java new file mode 100644 index 0000000..b297d70 --- /dev/null +++ b/dictionary/src/main/java/org/apache/kylin/dict/IDictionaryValueEnumerator.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.dict; + +import org.apache.calcite.linq4j.Enumerator; +import org.apache.hadoop.fs.RemoteIterator; + +import java.io.IOException; + +/** + * Created by dongli on 10/28/15. + */ +public interface IDictionaryValueEnumerator { + byte[] current() throws IOException; + + boolean moveNext() throws IOException; + + void close() throws IOException; +} diff --git a/dictionary/src/main/java/org/apache/kylin/dict/MultipleDictionaryValueEnumerator.java b/dictionary/src/main/java/org/apache/kylin/dict/MultipleDictionaryValueEnumerator.java new file mode 100644 index 0000000..7321bde --- /dev/null +++ b/dictionary/src/main/java/org/apache/kylin/dict/MultipleDictionaryValueEnumerator.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.dict; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.apache.kylin.common.util.Bytes; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; + +/** + * Created by dongli on 10/28/15. + */ +public class MultipleDictionaryValueEnumerator implements IDictionaryValueEnumerator { + private HashSet dedup = Sets.newHashSet(); + private int curDictIndex = 0; + private Dictionary curDict; + private int curKey; + private byte[] curValue = null; + private List dictionaryList; + + public MultipleDictionaryValueEnumerator(List dictionaryInfoList) { + dictionaryList = Lists.newArrayListWithCapacity(dictionaryInfoList.size()); + for (DictionaryInfo dictInfo : dictionaryInfoList) { + dictionaryList.add(dictInfo.getDictionaryObject()); + } + if (!dictionaryList.isEmpty()) { + curDict = dictionaryList.get(0); + curKey = curDict.getMinId(); + } + } + + @Override + public byte[] current() throws IOException { + return curValue; + } + + @Override + public boolean moveNext() throws IOException { + if (curDictIndex < dictionaryList.size() && curKey <= curDict.getMaxId()) { + byte[] buffer = new byte[curDict.getSizeOfValue()]; + int size = curDict.getValueBytesFromId(curKey, buffer, 0); + curValue = Bytes.copy(buffer, 0, size); + + if (++curKey > curDict.getMaxId()) { + if (++curDictIndex < dictionaryList.size()) { + curDict = dictionaryList.get(curDictIndex); + curKey = curDict.getMinId(); + } + } + + if (dedup.contains(curValue)) { + return moveNext(); + } else { + return true; + } + } + curValue = null; + return false; + } + + @Override + public void close() throws IOException { + } +} diff --git a/dictionary/src/main/java/org/apache/kylin/dict/TableColumnValueEnumerator.java b/dictionary/src/main/java/org/apache/kylin/dict/TableColumnValueEnumerator.java new file mode 100644 index 0000000..64a0e39 --- /dev/null +++ b/dictionary/src/main/java/org/apache/kylin/dict/TableColumnValueEnumerator.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.dict; + +import com.google.common.collect.Sets; +import org.apache.kylin.common.util.Bytes; +import org.apache.kylin.dict.lookup.ReadableTable; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Set; + +/** + * Created by dongli on 10/28/15. + */ +public class TableColumnValueEnumerator implements IDictionaryValueEnumerator { + + private ReadableTable.TableReader reader; + private int colIndex; + private byte[] colValue; + private Set dedup = Sets.newHashSet(); + + public TableColumnValueEnumerator(ReadableTable.TableReader reader, int colIndex) { + this.reader = reader; + this.colIndex = colIndex; + } + + @Override + public boolean moveNext() throws IOException { + if (reader.next()) { + String colStrValue; + String[] split = reader.getRow(); + if (split.length == 1) { + colStrValue = split[0]; + } else { + // normal case + if (split.length <= colIndex) { + throw new ArrayIndexOutOfBoundsException("Column no. " + colIndex + " not found, line split is " + Arrays.asList(split)); + } + colStrValue = split[colIndex]; + } + if (!dedup.contains(colStrValue)) { + dedup.add(colStrValue); + colValue = Bytes.toBytes(colStrValue); + return true; + } else { + return moveNext(); + } + + } else { + colValue = null; + return false; + } + } + + @Override + public void close() throws IOException { + if (reader != null) + reader.close(); + } + + @Override + public byte[] current() { + return colValue; + } +} diff --git a/dictionary/src/main/java/org/apache/kylin/dict/TrieDictionary.java b/dictionary/src/main/java/org/apache/kylin/dict/TrieDictionary.java index 1ecdf32..3479dbd 100644 --- a/dictionary/src/main/java/org/apache/kylin/dict/TrieDictionary.java +++ b/dictionary/src/main/java/org/apache/kylin/dict/TrieDictionary.java @@ -101,7 +101,7 @@ public class TrieDictionary extends Dictionary { this.maxValueLength = headIn.readShort(); String converterName = headIn.readUTF(); - if (converterName.isEmpty() == false) + if (!converterName.isEmpty()) this.bytesConvert = (BytesConverter) ClassUtil.forName(converterName, BytesConverter.class).newInstance(); this.nValues = BytesUtil.readUnsigned(trieBytes, headSize + sizeChildOffset, sizeNoValuesBeneath); @@ -392,7 +392,7 @@ public class TrieDictionary extends Dictionary { @Override public boolean equals(Object o) { - if ((o instanceof TrieDictionary) == false) { + if (!(o instanceof TrieDictionary)) { logger.info("Equals return false because o is not TrieDictionary"); return false; } diff --git a/dictionary/src/main/java/org/apache/kylin/dict/TrieDictionaryBuilder.java b/dictionary/src/main/java/org/apache/kylin/dict/TrieDictionaryBuilder.java index 2145f71..302045a 100644 --- a/dictionary/src/main/java/org/apache/kylin/dict/TrieDictionaryBuilder.java +++ b/dictionary/src/main/java/org/apache/kylin/dict/TrieDictionaryBuilder.java @@ -479,10 +479,10 @@ public class TrieDictionaryBuilder { int o = head.length; offsetMap.put(root, o); o = build_writeNode(root, o, true, sizeNoValuesBeneath, sizeChildOffset, trieBytes); - if (root.children.isEmpty() == false) + if (!root.children.isEmpty()) open.addLast(root); - while (open.isEmpty() == false) { + while (!open.isEmpty()) { Node parent = open.removeFirst(); build_overwriteChildOffset(offsetMap.get(parent), o - head.length, sizeChildOffset, trieBytes); for (int i = 0; i < parent.children.size(); i++) { @@ -490,7 +490,7 @@ public class TrieDictionaryBuilder { boolean isLastChild = (i == parent.children.size() - 1); offsetMap.put(c, o); o = build_writeNode(c, o, isLastChild, sizeNoValuesBeneath, sizeChildOffset, trieBytes); - if (c.children.isEmpty() == false) + if (!c.children.isEmpty()) open.addLast(c); } } diff --git a/dictionary/src/main/java/org/apache/kylin/dict/lookup/ListDictionaryValueEnumerator.java b/dictionary/src/main/java/org/apache/kylin/dict/lookup/ListDictionaryValueEnumerator.java new file mode 100644 index 0000000..581ee43 --- /dev/null +++ b/dictionary/src/main/java/org/apache/kylin/dict/lookup/ListDictionaryValueEnumerator.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.kylin.dict.lookup; + +import org.apache.kylin.dict.IDictionaryValueEnumerator; + +import java.io.IOException; +import java.util.List; +import java.util.ListIterator; + +/** + * Created by dongli on 10/28/15. + */ +public class ListDictionaryValueEnumerator implements IDictionaryValueEnumerator { + ListIterator listIterator; + + public ListDictionaryValueEnumerator(List list) { + listIterator = list.listIterator(); + } + + @Override + public byte[] current() throws IOException { + return listIterator.next(); + } + + @Override + public boolean moveNext() throws IOException { + return listIterator.hasNext(); + } + + @Override + public void close() throws IOException { + } +} diff --git a/job/src/test/java/org/apache/kylin/job/hadoop/cube/MergeCuboidMapperTest.java b/job/src/test/java/org/apache/kylin/job/hadoop/cube/MergeCuboidMapperTest.java index d801df5..7a2ebad 100644 --- a/job/src/test/java/org/apache/kylin/job/hadoop/cube/MergeCuboidMapperTest.java +++ b/job/src/test/java/org/apache/kylin/job/hadoop/cube/MergeCuboidMapperTest.java @@ -38,6 +38,7 @@ import org.apache.kylin.dict.DictionaryGenerator; import org.apache.kylin.dict.DictionaryInfo; import org.apache.kylin.dict.DictionaryManager; import org.apache.kylin.dict.TrieDictionary; +import org.apache.kylin.dict.lookup.ListDictionaryValueEnumerator; import org.apache.kylin.dict.lookup.ReadableTable.TableSignature; import org.apache.kylin.metadata.MetadataManager; import org.apache.kylin.metadata.model.TblColRef; @@ -76,7 +77,7 @@ public class MergeCuboidMapperTest extends LocalFileMetadataTestCase { List values = new ArrayList(); values.add(new byte[] { 101, 101, 101 }); values.add(new byte[] { 102, 102, 102 }); - Dictionary dict = DictionaryGenerator.buildDictionaryFromValueList(newDictInfo, values); + Dictionary dict = DictionaryGenerator.buildDictionaryFromValueEnumerator(newDictInfo, new ListDictionaryValueEnumerator(values)); dictionaryManager.trySaveNewDict(dict, newDictInfo); ((TrieDictionary) dict).dump(System.out); @@ -128,7 +129,7 @@ public class MergeCuboidMapperTest extends LocalFileMetadataTestCase { values.add(new byte[] { 99, 99, 99 }); else values.add(new byte[] { 98, 98, 98 }); - Dictionary dict = DictionaryGenerator.buildDictionaryFromValueList(newDictInfo, values); + Dictionary dict = DictionaryGenerator.buildDictionaryFromValueEnumerator(newDictInfo, new ListDictionaryValueEnumerator(values)); dictionaryManager.trySaveNewDict(dict, newDictInfo); ((TrieDictionary) dict).dump(System.out); -- 2.3.8 (Apple Git-58)