--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.ValueNodeList\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.compile;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.types.DataTypeDescriptor;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.StringDataValue;\r
+import org.apache.derby.iapi.types.TypeId;\r
+\r
+import org.apache.derby.iapi.sql.compile.TypeCompiler;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.store.access.Qualifier;\r
+\r
+import org.apache.derby.iapi.util.JBitSet;\r
+\r
+import java.util.Vector;\r
+\r
+/**\r
+ * A ValueNodeList represents a list of ValueNodes within a specific predicate \r
+ * (eg, IN list, NOT IN list or BETWEEN) in a DML statement. \r
+ * It extends QueryTreeNodeVector.\r
+ *\r
+ */\r
+\r
+public class ValueNodeList extends QueryTreeNodeVector\r
+{\r
+\r
+ /**\r
+ * Prints the sub-nodes of this object. See QueryTreeNode.java for\r
+ * how tree printing is supposed to work.\r
+ *\r
+ * @param depth The depth of this node in the tree\r
+ */\r
+\r
+ public void printSubNodes(int depth)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ super.printSubNodes(depth);\r
+\r
+ for (int index = 0; index < size(); index++)\r
+ {\r
+ ValueNode valueNode;\r
+ valueNode = (ValueNode) elementAt(index);\r
+ valueNode.treePrint(depth + 1);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Add a ValueNode to the list.\r
+ *\r
+ * @param valueNode A ValueNode to add to the list\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void addValueNode(ValueNode valueNode) throws StandardException\r
+ {\r
+ addElement(valueNode);\r
+ }\r
+\r
+ /**\r
+ * Bind this expression. This means binding the sub-expressions,\r
+ * as well as figuring out what the return type is for this expression.\r
+ *\r
+ * @param fromList The FROM list for the query this\r
+ * expression is in, for binding columns.\r
+ * @param subqueryList The subquery list being built as we find SubqueryNodes\r
+ * @param aggregateVector The aggregate vector being built as we find AggregateNodes\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void bindExpression(FromList fromList, \r
+ SubqueryList subqueryList,\r
+ Vector aggregateVector)\r
+ throws StandardException\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ ValueNode vn = (ValueNode) elementAt(index);\r
+ vn = vn.bindExpression(fromList, subqueryList,\r
+ aggregateVector);\r
+\r
+ setElementAt(vn, index);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Generate a SQL->Java->SQL conversion tree any node in the list\r
+ * which is not a system built-in type.\r
+ * This is useful when doing comparisons, built-in functions, etc. on\r
+ * java types which have a direct mapping to system built-in types.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void genSQLJavaSQLTrees()\r
+ throws StandardException\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ ValueNode valueNode = (ValueNode) elementAt(index);\r
+ \r
+ if (valueNode.getTypeId().userType())\r
+ {\r
+ setElementAt(valueNode.genSQLJavaSQLTree(), index);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get the dominant DataTypeServices from the elements in the list. This\r
+ * method will also set the correct collation information on the dominant\r
+ * DataTypeService if we are dealing with character string datatypes.\r
+ * \r
+ * Algorithm for determining collation information\r
+ * This method will check if it is dealing with character string datatypes.\r
+ * If yes, then it will check if all the character string datatypes have\r
+ * the same collation derivation and collation type associated with them.\r
+ * If not, then the resultant DTD from this method will have collation\r
+ * derivation of NONE. If yes, then the resultant DTD from this method will\r
+ * have the same collation derivation and collation type as all the \r
+ * character string datatypes.\r
+ * \r
+ * Note that this method calls DTD.getDominantType and that method returns\r
+ * the dominant type of the 2 DTDs involved in this method. That method \r
+ * sets the collation info on the dominant type following the algorithm\r
+ * mentioned in the comments of \r
+ * @see DataTypeDescriptor#getDominantType(DataTypeDescriptor, ClassFactory)\r
+ * With that algorithm, if one DTD has collation derivation of NONE and the\r
+ * other DTD has collation derivation of IMPLICIT, then the return DTD from\r
+ * DTD.getDominantType will have collation derivation of IMPLICIT. That is \r
+ * not the correct algorithm for aggregate operators. SQL standards says\r
+ * that if EVERY type has implicit derivation AND is of the same type, then \r
+ * the collation of the resultant will be of that type with derivation \r
+ * IMPLICIT. To provide this behavior for aggregate operator, we basically \r
+ * ignore the collation type and derivation picked by \r
+ * DataTypeDescriptor.getDominantType. Instead we let \r
+ * getDominantTypeServices use the simple algorithm listed at the top of\r
+ * this method's comments to determine the collation type and derivation \r
+ * for this ValueNodeList object.\r
+ * \r
+ * @return DataTypeServices The dominant DataTypeServices.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public DataTypeDescriptor getDominantTypeServices() throws StandardException\r
+ {\r
+ DataTypeDescriptor dominantDTS = null;\r
+ //Following 2 will hold the collation derivation and type of the first \r
+ //string operand. This collation information will be checked against\r
+ //the collation derivation and type of other string operands. If a \r
+ //mismatch is found, foundCollationMisMatch will be set to true.\r
+ int firstCollationDerivation = -1;\r
+ int firstCollationType = -1;\r
+ //As soon as we find 2 strings with different collations, we set the \r
+ //following flag to true. At the end of the method, if this flag is set \r
+ //to true then it means that we have operands with different collation\r
+ //types and hence the resultant dominant type will have to have the\r
+ //collation derivation of NONE. \r
+ boolean foundCollationMisMatch = false;\r
+\r
+ for (int index = 0; index < size(); index++)\r
+ {\r
+ ValueNode valueNode;\r
+\r
+ valueNode = (ValueNode) elementAt(index);\r
+ if (valueNode.requiresTypeFromContext())\r
+ continue;\r
+ DataTypeDescriptor valueNodeDTS = valueNode.getTypeServices();\r
+\r
+ if (valueNodeDTS.getTypeId().isStringTypeId())\r
+ {\r
+ if (firstCollationDerivation == -1)\r
+ {\r
+ //found first string type. Initialize firstCollationDerivation\r
+ //and firstCollationType with collation information from \r
+ //that first string type operand.\r
+ firstCollationDerivation = valueNodeDTS.getCollationDerivation(); \r
+ firstCollationType = valueNodeDTS.getCollationType(); \r
+ } else if (!foundCollationMisMatch)\r
+ {\r
+ if (firstCollationDerivation != valueNodeDTS.getCollationDerivation())\r
+ foundCollationMisMatch = true;//collation derivations don't match\r
+ else if (firstCollationType != valueNodeDTS.getCollationType())\r
+ foundCollationMisMatch = true;//collation types don't match\r
+ }\r
+ }\r
+ if (dominantDTS == null)\r
+ {\r
+ dominantDTS = valueNodeDTS;\r
+ }\r
+ else\r
+ {\r
+ dominantDTS = dominantDTS.getDominantType(valueNodeDTS, getClassFactory());\r
+ }\r
+ }\r
+\r
+ //if following if returns true, then it means that we are dealing with \r
+ //string operands.\r
+ if (firstCollationDerivation != -1)\r
+ {\r
+ if (foundCollationMisMatch) {\r
+ //if we come here that it means that alll the string operands\r
+ //do not have matching collation information on them. Hence the\r
+ //resultant dominant DTD should have collation derivation of \r
+ //NONE.\r
+ dominantDTS.setCollationDerivation(StringDataValue.COLLATION_DERIVATION_NONE);\r
+ } \r
+ //if we didn't find any collation mismatch, then resultant dominant\r
+ //DTD already has the correct collation information on it and hence\r
+ //we don't need to do anything.\r
+ }\r
+\r
+ return dominantDTS;\r
+ }\r
+\r
+ /**\r
+ * Get the first non-null DataTypeServices from the elements in the list.\r
+ *\r
+ * @return DataTypeServices The first non-null DataTypeServices.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public DataTypeDescriptor getTypeServices() throws StandardException\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ ValueNode valueNode = (ValueNode) elementAt(index);\r
+ DataTypeDescriptor valueNodeDTS = valueNode.getTypeServices();\r
+\r
+ if (valueNodeDTS != null)\r
+ {\r
+ return valueNodeDTS;\r
+ }\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ /**\r
+ * Return whether or not all of the entries in the list have the same\r
+ * type precendence as the specified value.\r
+ *\r
+ * @param precedence The specified precedence.\r
+ *\r
+ * @return Whether or not all of the entries in the list have the same\r
+ * type precendence as the specified value.\r
+ */\r
+ boolean allSamePrecendence(int precedence)\r
+ throws StandardException\r
+ {\r
+ boolean allSame = true;\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ ValueNode valueNode;\r
+\r
+ valueNode = (ValueNode) elementAt(index);\r
+ DataTypeDescriptor valueNodeDTS = valueNode.getTypeServices();\r
+\r
+ if (valueNodeDTS == null)\r
+ {\r
+ return false;\r
+ }\r
+\r
+ if (precedence != valueNodeDTS.getTypeId().typePrecedence())\r
+ {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ return allSame;\r
+ }\r
+\r
+\r
+ /**\r
+ * Make sure that passed ValueNode's type is compatible with the non-parameter elements in the ValueNodeList.\r
+ *\r
+ * @param leftOperand Check for compatibility against this parameter's type\r
+ *\r
+ */\r
+ public void compatible(ValueNode leftOperand) throws StandardException\r
+ {\r
+ int size = size();\r
+ TypeId leftType;\r
+ ValueNode valueNode;\r
+ TypeCompiler leftTC;\r
+\r
+ leftType = leftOperand.getTypeId();\r
+ leftTC = leftOperand.getTypeCompiler();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ valueNode = (ValueNode) elementAt(index);\r
+ if (valueNode.requiresTypeFromContext())\r
+ continue;\r
+\r
+\r
+ /*\r
+ ** Are the types compatible to each other? If not, throw an exception.\r
+ */\r
+ if (! leftTC.compatible(valueNode.getTypeId()))\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_DB2_COALESCE_DATATYPE_MISMATCH,\r
+ leftType.getSQLTypeName(),\r
+ valueNode.getTypeId().getSQLTypeName()\r
+ );\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Determine whether or not the leftOperand is comparable() with all of\r
+ * the elements in the list. Throw an exception if any of them are not \r
+ * comparable.\r
+ *\r
+ * @param leftOperand The left side of the expression\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void comparable(ValueNode leftOperand) throws StandardException\r
+ {\r
+ int size = size();\r
+ TypeId leftType;\r
+ ValueNode valueNode;\r
+\r
+ leftType = leftOperand.getTypeId();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ valueNode = (ValueNode) elementAt(index);\r
+\r
+ /*\r
+ ** Can the types be compared to each other? If not, throw an\r
+ ** exception.\r
+ */\r
+ if (! leftOperand.getTypeServices().comparable(valueNode.getTypeServices(),\r
+ false,\r
+ getClassFactory()))\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_NOT_COMPARABLE, \r
+ leftOperand.getTypeServices().getSQLTypeNameWithCollation(),\r
+ valueNode.getTypeServices().getSQLTypeNameWithCollation()\r
+ );\r
+ }\r
+ }\r
+ }\r
+\r
+ /** \r
+ * Determine whether or not any of the elements in the list are nullable.\r
+ *\r
+ * @return boolean Whether or not any of the elements in the list \r
+ * are nullable.\r
+ */\r
+ public boolean isNullable()\r
+ throws StandardException\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ if (((ValueNode) elementAt(index)).getTypeServices().isNullable())\r
+ {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+ \r
+ /**\r
+ * Does this list contain a ParameterNode?\r
+ *\r
+ * @return boolean Whether or not the list contains a ParameterNode\r
+ */\r
+ public boolean containsParameterNode()\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ if (((ValueNode) elementAt(index)).requiresTypeFromContext())\r
+ {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+ \r
+ /**\r
+ * Does this list contain all ParameterNodes?\r
+ *\r
+ * @return boolean Whether or not the list contains all ParameterNodes\r
+ */\r
+ public boolean containsAllParameterNodes()\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ if (! (((ValueNode) elementAt(index)).requiresTypeFromContext()))\r
+ {\r
+ return false;\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Does this list contain all ConstantNodes?\r
+ *\r
+ * @return boolean Whether or not the list contains all ConstantNodes\r
+ */\r
+ public boolean containsAllConstantNodes()\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ if (! ((ValueNode) elementAt(index) instanceof ConstantNode))\r
+ {\r
+ return false;\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Does this list *only* contain constant and/or parameter nodes?\r
+ *\r
+ * @return boolean True if every node in this list is either a constant\r
+ * node or parameter node.\r
+ */\r
+ public boolean containsOnlyConstantAndParamNodes()\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ ValueNode vNode = (ValueNode)elementAt(index);\r
+ if (!vNode.requiresTypeFromContext() &&\r
+ !(vNode instanceof ConstantNode))\r
+ {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Sort the entries in the list in ascending order.\r
+ * (All values are assumed to be constants.)\r
+ *\r
+ * @param judgeODV In case of type not exactly matching, the judging type.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ void sortInAscendingOrder(DataValueDescriptor judgeODV)\r
+ throws StandardException\r
+ {\r
+ int size = size();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(size > 0,\r
+ "size() expected to be non-zero");\r
+ }\r
+\r
+ /* We use bubble sort to sort the list since we expect\r
+ * the list to be in sorted order > 90% of the time.\r
+ */\r
+ boolean continueSort = true;\r
+\r
+ while (continueSort)\r
+ {\r
+ continueSort = false;\r
+ \r
+ for (int index = 1; index < size; index++)\r
+ {\r
+ ConstantNode currCN = (ConstantNode) elementAt(index);\r
+ DataValueDescriptor currODV =\r
+ currCN.getValue();\r
+ ConstantNode prevCN = (ConstantNode) elementAt(index - 1);\r
+ DataValueDescriptor prevODV =\r
+ prevCN.getValue();\r
+\r
+ /* Swap curr and prev if prev > curr */\r
+ if ((judgeODV == null && (prevODV.compare(currODV)) > 0) ||\r
+ (judgeODV != null && judgeODV.greaterThan(prevODV, currODV).equals(true)))\r
+ {\r
+ setElementAt(currCN, index - 1);\r
+ setElementAt(prevCN, index);\r
+ continueSort = true;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Set the descriptor for every ParameterNode in the list.\r
+ *\r
+ * @param descriptor The DataTypeServices to set for the parameters\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void setParameterDescriptor(DataTypeDescriptor descriptor)\r
+ throws StandardException\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ ValueNode valueNode = (ValueNode) elementAt(index);\r
+ if (valueNode.requiresTypeFromContext())\r
+ {\r
+ valueNode.setType(descriptor);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Preprocess a ValueNodeList. For now, we just preprocess each ValueNode\r
+ * in the list.\r
+ *\r
+ * @param numTables Number of tables in the DML Statement\r
+ * @param outerFromList FromList from outer query block\r
+ * @param outerSubqueryList SubqueryList from outer query block\r
+ * @param outerPredicateList PredicateList from outer query block\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void preprocess(int numTables,\r
+ FromList outerFromList,\r
+ SubqueryList outerSubqueryList,\r
+ PredicateList outerPredicateList) \r
+ throws StandardException\r
+ {\r
+ int size = size();\r
+ ValueNode valueNode;\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ valueNode = (ValueNode) elementAt(index);\r
+ valueNode.preprocess(numTables,\r
+ outerFromList, outerSubqueryList,\r
+ outerPredicateList);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Remap all ColumnReferences in this tree to be clones of the\r
+ * underlying expression.\r
+ *\r
+ * @return ValueNodeList The remapped expression tree.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public ValueNodeList remapColumnReferencesToExpressions()\r
+ throws StandardException\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ setElementAt(\r
+ ((ValueNode) elementAt(index)).remapColumnReferencesToExpressions(),\r
+ index);\r
+ }\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * Return whether or not this expression tree represents a constant expression.\r
+ *\r
+ * @return Whether or not this expression tree represents a constant expression.\r
+ */\r
+ public boolean isConstantExpression()\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ boolean retcode;\r
+\r
+ retcode = ((ValueNode) elementAt(index)).isConstantExpression();\r
+ if (! retcode)\r
+ {\r
+ return retcode;\r
+ }\r
+ }\r
+\r
+ return true;\r
+ }\r
+\r
+ /** @see ValueNode#constantExpression */\r
+ public boolean constantExpression(PredicateList whereClause)\r
+ {\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ boolean retcode;\r
+\r
+ retcode =\r
+ ((ValueNode) elementAt(index)).constantExpression(whereClause);\r
+ if (! retcode)\r
+ {\r
+ return retcode;\r
+ }\r
+ }\r
+\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Categorize this predicate. Initially, this means\r
+ * building a bit map of the referenced tables for each predicate.\r
+ * If the source of this ColumnReference (at the next underlying level) \r
+ * is not a ColumnReference or a VirtualColumnNode then this predicate\r
+ * will not be pushed down.\r
+ *\r
+ * For example, in:\r
+ * select * from (select 1 from s) a (x) where x = 1\r
+ * we will not push down x = 1.\r
+ * NOTE: It would be easy to handle the case of a constant, but if the\r
+ * inner SELECT returns an arbitrary expression, then we would have to copy\r
+ * that tree into the pushed predicate, and that tree could contain\r
+ * subqueries and method calls.\r
+ * RESOLVE - revisit this issue once we have views.\r
+ *\r
+ * @param referencedTabs JBitSet with bit map of referenced FromTables\r
+ * @param simplePredsOnly Whether or not to consider method\r
+ * calls, field references and conditional nodes\r
+ * when building bit map\r
+ *\r
+ * @return boolean Whether or not source.expression is a ColumnReference\r
+ * or a VirtualColumnNode.\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public boolean categorize(JBitSet referencedTabs, boolean simplePredsOnly)\r
+ throws StandardException\r
+ {\r
+ /* We stop here when only considering simple predicates\r
+ * as we don't consider in lists when looking\r
+ * for null invariant predicates.\r
+ */\r
+ boolean pushable = true;\r
+ int size = size();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ pushable = ((ValueNode) elementAt(index)).categorize(referencedTabs, simplePredsOnly) &&\r
+ pushable;\r
+ }\r
+\r
+ return pushable;\r
+ }\r
+\r
+ /**\r
+ * Return the variant type for the underlying expression.\r
+ * The variant type can be:\r
+ * VARIANT - variant within a scan\r
+ * (method calls and non-static field access)\r
+ * SCAN_INVARIANT - invariant within a scan\r
+ * (column references from outer tables)\r
+ * QUERY_INVARIANT - invariant within the life of a query\r
+ * CONSTANT - constant\r
+ *\r
+ * @return The variant type for the underlying expression.\r
+ * @exception StandardException thrown on error\r
+ */\r
+ protected int getOrderableVariantType() throws StandardException\r
+ {\r
+ int listType = Qualifier.CONSTANT;\r
+ int size = size();\r
+\r
+ /* If any element in the list is VARIANT then the \r
+ * entire expression is variant\r
+ * else it is SCAN_INVARIANT if any element is SCAN_INVARIANT\r
+ * else it is QUERY_INVARIANT.\r
+ */\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ int curType = ((ValueNode) elementAt(index)).getOrderableVariantType();\r
+ listType = Math.min(listType, curType);\r
+ }\r
+\r
+ return listType;\r
+ }\r
+}\r