Package nexj.core.persistence

Source Code of nexj.core.persistence.Query$QueryDepVisitor

// Copyright 2010-2011 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.persistence;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import nexj.core.meta.Attribute;
import nexj.core.meta.Metaclass;
import nexj.core.meta.MetadataException;
import nexj.core.meta.Primitive;
import nexj.core.meta.PrivilegeSet;
import nexj.core.meta.Type;
import nexj.core.meta.TypeMismatchException;
import nexj.core.meta.persistence.AttributeMapping;
import nexj.core.meta.persistence.ClassMapping;
import nexj.core.meta.persistence.DataSourceFragment;
import nexj.core.meta.persistence.Key;
import nexj.core.meta.persistence.PersistenceMapping;
import nexj.core.persistence.operator.AggregateOperator;
import nexj.core.persistence.operator.AndOperator;
import nexj.core.persistence.operator.AnyOperator;
import nexj.core.persistence.operator.AttributeOperator;
import nexj.core.persistence.operator.BinaryOperator;
import nexj.core.persistence.operator.ComparisonOperator;
import nexj.core.persistence.operator.ConstantOperator;
import nexj.core.persistence.operator.CountOperator;
import nexj.core.persistence.operator.DivisionOperator;
import nexj.core.persistence.operator.EqualsOperator;
import nexj.core.persistence.operator.FunctionOperator;
import nexj.core.persistence.operator.GreaterThanOperator;
import nexj.core.persistence.operator.GreaterThanOrEqualsOperator;
import nexj.core.persistence.operator.IfOperator;
import nexj.core.persistence.operator.InOperator;
import nexj.core.persistence.operator.IntrinsicFunctionOperator;
import nexj.core.persistence.operator.LessThanOperator;
import nexj.core.persistence.operator.LessThanOrEqualsOperator;
import nexj.core.persistence.operator.LikeOperator;
import nexj.core.persistence.operator.Logical;
import nexj.core.persistence.operator.LookupAggregateOperator;
import nexj.core.persistence.operator.MatchOperator;
import nexj.core.persistence.operator.MinusOperator;
import nexj.core.persistence.operator.MultiArgOperator;
import nexj.core.persistence.operator.MultiplicationOperator;
import nexj.core.persistence.operator.NegationOperator;
import nexj.core.persistence.operator.NotEqualsOperator;
import nexj.core.persistence.operator.NotOperator;
import nexj.core.persistence.operator.NumericAggregateOperator;
import nexj.core.persistence.operator.OrOperator;
import nexj.core.persistence.operator.PlusOperator;
import nexj.core.persistence.operator.Quantor;
import nexj.core.persistence.operator.SumOperator;
import nexj.core.persistence.operator.TypeConversionOperator;
import nexj.core.persistence.operator.UnaryOperator;
import nexj.core.persistence.operator.UniqueOperator;
import nexj.core.rpc.InstanceFactory;
import nexj.core.rpc.RPCUtil;
import nexj.core.rpc.TransferObject;
import nexj.core.runtime.DataVolumeException;
import nexj.core.runtime.Instance;
import nexj.core.runtime.InstanceArrayList;
import nexj.core.runtime.InstanceList;
import nexj.core.runtime.InvocationContext;
import nexj.core.runtime.UnitOfWork;
import nexj.core.scripting.Compiler;
import nexj.core.scripting.Function;
import nexj.core.scripting.Intrinsic;
import nexj.core.scripting.IntrinsicFunction;
import nexj.core.scripting.Machine;
import nexj.core.scripting.Macro;
import nexj.core.scripting.Pair;
import nexj.core.scripting.ScriptingException;
import nexj.core.scripting.Symbol;
import nexj.core.scripting.match.ExpressionParser;
import nexj.core.scripting.match.MatchNode;
import nexj.core.util.ArrayIterator;
import nexj.core.util.EmptyIterator;
import nexj.core.util.HashHolder;
import nexj.core.util.HashTab;
import nexj.core.util.LinkedHashTab;
import nexj.core.util.Logger;
import nexj.core.util.Lookup;
import nexj.core.util.Named;
import nexj.core.util.ObjUtil;
import nexj.core.util.PrintWriter;
import nexj.core.util.Printable;
import nexj.core.util.PropertyMap;
import nexj.core.util.SysUtil;
import nexj.core.util.Undefined;

/**
* Query node representing a set of class instances of the same base type.
*/
public class Query extends Source implements Comparator, Printable
{
   // constants

   /**
    * Default query association key.
    */
   public final static Object ASSOC_QUERY = "QUERY";

   /**
    * Where clause association key.
    */
   public final static Object ASSOC_WHERE = "WHERE";

   /**
    * Visit the query associations.
    */
   public final static int VISIT_QUERY = 0x0001;

   /**
    * Visit the where associations.
    */
   public final static int VISIT_WHERE = 0x0002;

   /**
    * Visit the quantor query nodes.
    */
   public final static int VISIT_QUANTOR = 0x0004;

   /**
    * Visit all the associations.
    */
   public final static int VISIT_ALL = VISIT_QUERY | VISIT_WHERE | VISIT_QUANTOR;

   /**
    * Default security clause is applied.
    */
   public final static byte SEC_DEFAULT = -1;

   /**
    * No security clause applied.
    */
   public final static byte SEC_NONE = 0;

   /**
    * Security clause applied only to the current query node.
    */
   public final static byte SEC_NODE = 1;

   /**
    * Security clause applied to all the current query node and its sub-nodes.
    */
   public final static byte SEC_ALL = 2;

   /**
    * Unknown output mode.
    */
   public final static byte OUTPUT_UNKNOWN = -1;

   /**
    * The query node does not output any instances.
    */
   public final static byte OUTPUT_NONE = 0;

   /**
    * The query node outputs instances without querying for them
    * (e.g. only the OID is retrieved from another instance).
    */
   public final static byte OUTPUT_LAZY = 1;

   /**
    * The query node outputs instances with eager type code and locking loading.
    */
   public final static byte OUTPUT_EAGER = 2;

   /**
    * The query node is participating in a where clause.
    */
   public final static byte RESTRICTION_WHERE = 0x01;

   /**
    * The query node is participating in an order by clause.
    */
   public final static byte RESTRICTION_ORDERBY = 0x02;

   /**
    * The query node passes the restriction to the children.
    */
   public final static byte RESTRICTION_PARENT = 0x40;

   /**
    * The query node is tentatively required (used for required flag computation).
    */
   private final static byte RESTRICTION_REQUIRED = 0x04;

   /**
    * The query node is tentatively not required (used for required flag computation).
    */
   private final static byte RESTRICTION_NOT_REQUIRED = 0x08;

   /**
    * Unlimited timeout.
    */
   public final static int TIMEOUT_UNLIMITED = 0;

   /**
    * The timeout configured in the data source.
    */
   public final static int TIMEOUT_AUTO = -1;

   /**
    * Transfer object used to indicate a null cached instance.
    */
   protected final static TransferObject NULL_TO = new TransferObject(0);

   // attributes

   /**
    * The order by expression count.
    */
   private int m_nOrderByCount;

   /**
    * The group by expression count.
    */
   private int m_nGroupByCount;

   /**
    * The maximum instance count.
    */
   private int m_nMaxCount = -1;

   /**
    * The limit of the number of instances retrieved by a
    * single read() operation (negative for unlimited, 0 for default).
    */
   private int m_nLimit;

   /**
    * The query timeout in seconds (0 for unlimited, negative to use the default).
    */
   private int m_nTimeout = TIMEOUT_AUTO;

   /**
    * Number of retrieved instances to skip from the beginning.
    */
   private int m_nOffset;

   /**
    * Predecessor count for topological sorting.
    */
   private int m_nPredCount;

   /**
    * The security clause mode, one of the SEC_* constants.
    */
   private byte m_nSecurity;

   /**
    * The output mode, one of the OUTPUT_* constants.
    */
   private byte m_nOutput;

   /**
    * The restriction mode (combination of RESTRICTION_* flags).
    */
   private byte m_nRestriction;

   /**
    * 1 if the query selects a unique instance, 0 otherwise, -1 if unknown.
    */
   private byte m_nUnique = -1;

   /**
    * The caching flag (-1 means not set).
    */
   private byte m_nCached = -1;

   /**
    * True if an inverse attribute mapping is used.
    */
   private boolean m_bInverse;

   /**
    * True if the query node is required. This can be different from
    * the attribute required flag, as the persistence root is used.
    */
   private boolean m_bRequired;

   /**
    * True if the query where clause includes type code.
    */
   private boolean m_bTypeCodeFiltered;

   /**
    * True to lock the retrieved instances exclusively.
    */
   private boolean m_bLocking;

   /**
    * True if this is a subquery (for persistence mapping purposes).
    */
   private boolean m_bSubquery;

   /**
    * The flag indicating that the engine returns no more than the requested number of instances.
    */
   private boolean m_bLimited;

   /**
    * True if the query contains subcollections with the same root.
    */
   private boolean m_bPlural;

   /**
    * True if query has a match operator.
    */
   private boolean m_bMatch;

   /**
    * True if the query preserves the instance identity.
    */
   private boolean m_bIdentity = true;

   /**
    * The cursor retrieval flag.
    */
   private boolean m_bCursor;

   // associations

   /**
    * The query node metaclass.
    */
   private Metaclass m_metaclass;

   /**
    * The association where clause.
    * Null unless it has been specified on the association path: (@ attr1 filter ...)
    */
   private Object m_filter;

   /**
    * The persistence mapping. Cannot be null.
    */
   private PersistenceMapping m_persistenceMapping;

   /**
    * The data source fragment.
    */
   private DataSourceFragment m_fragment;

   /**
    * The field map: Field[Attribute|Operator].
    */
   private Lookup m_fieldMap = new HashTab();

   /**
    * The first output field.
    */
   private Field m_firstOutputField;

   /**
    * The last output field.
    */
   private Field m_lastOutputField;

   /**
    * The type code field. Can be null.
    */
   private Field m_typeCodeField;

   /**
    * The locking field. Can be null.
    */
   private Field m_lockingField;

   /**
    * The root node of the subtree.
    */
   private Query m_root;

   /**
    * The parent query.
    */
   private Query m_parent;

   /**
    * The output query collection.
    */
   private List m_outputQueryList; // of type Query

   /**
    * The quantor root node of the subtree.
    */
   private Query m_quantorRoot;

   /**
    * The associated query array.
    */
   private Query[] m_queryArray;

   /**
    * The where clause query array: Query[].
    */
   private Query[] m_whereArray;

   /**
    * Dependency array for topological sorting.
    */
   private Query[] m_depArray;

   /**
    * The quantor query map of arrays: Query[][Operator].
    */
   private Lookup m_quantorMap;

   /**
    * The where clause.
    */
   private Operator m_where;

   /**
    * The having clause.
    */
   private Operator m_having;

   /**
    * The group by expressions.
    */
   private Operator[] m_groupByArray;

   /**
    * The order by expressions: Operator[2*n], Boolean[2*n+1].
    */
   private Operator[] m_orderByArray;

   /**
    * The query constraint.
    */
   private Operator m_constraint;

   /**
    * The query instance selection OID.
    */
   private OID m_oid;

   /**
    * The field item.
    */
   private Object m_fieldItem;

   /**
    * The parent persistence mapping item.
    */
   private Object m_parentItem;

   /**
    * The child persistence mapping item.
    */
   private Object m_childItem;

   /**
    * The query generator.
    */
   private Object m_generator;

   /**
    * The persistence adapter.
    */
   private PersistenceAdapter m_adapter;

   /**
    * The invocation context.
    */
   private InvocationContext m_context;

   /**
    * List of all the root query nodes: Query[].
    */
   private List m_rootList;

   /**
    * The source key OID to parent instance set map: Instance[][OID].
    */
   private Lookup m_parentInstanceMap;

   /**
    * The currently evaluated instance.
    */
   private PropertyMap m_instance;

   /**
    * Operator containing true.
    */
   private final static Operator TRUE_OPERATOR = new ConstantOperator(Boolean.TRUE);

   /**
    * Visitor for clearing the required restriction flags.
    */
   private final static Visitor REQUIRED_RESTRICTION_CLEANUP_VISITOR = new Visitor()
   {
      public boolean visit(Query query)
      {
         query.removeRestriction(RESTRICTION_REQUIRED | RESTRICTION_NOT_REQUIRED);

         return true;
      }
     
      public boolean postVisit(Query query)
      {
         return true;
      }
     
      public boolean isEligible(Query query)
      {
         return true;
      }
   };

   /**
    * Visitor for computing the required restrictions.
    */
   private final static Operator.Visitor REQUIRED_RESTRICTION_COMPUTING_VISITOR = new Operator.Visitor()
   {
      public boolean isEligible(Operator op)
      {
         return !(op instanceof IfOperator) && !(op instanceof Quantor);
      }

      public boolean visit(Operator op)
      {
         if (op.getOrdinal() == AttributeOperator.ORDINAL)
         {
            Source source = op.getSource();

            if (source != null)
            {
               Query query = source.getQuery();

               if (!query.isRequired())
               {
                  Boolean required = getRequired(op.getParent());

                  if (required != null)
                  {
                     query.addRestriction((required.booleanValue()) ? RESTRICTION_REQUIRED : RESTRICTION_NOT_REQUIRED);
                  }
               }
            }
         }

         return true;
      }

      public Boolean getRequired(Operator parent)
      {
         if (parent == null)
         {
            return Boolean.TRUE;
         }

         Boolean required = null;

         if (parent instanceof NotEqualsOperator)
         {
            required = Boolean.TRUE;
         }
         else if (parent instanceof EqualsOperator)
         {
            Operator op = ((EqualsOperator)parent).getRight();

            if (op.isConstant())
            {
               required = Boolean.valueOf(op.getValue() != null);
            }
         }

         if (required != null)
         {
            parent = parent.getParent();
         }

         while (parent != null)
         {
            if (parent instanceof InOperator)
            {
               if (required != null)
               {
                  return null;
               }

               required = Boolean.TRUE;
            }
            else if (parent instanceof ComparisonOperator)
            {
               if (required != null)
               {
                  return null;
               }

               Operator op = ((ComparisonOperator)parent).getRight();

               if (op.isConstant() && op.getValue() != null)
               {
                  required = Boolean.TRUE;
               }
            }
            else if (parent instanceof Logical)
            {
               if (parent instanceof AndOperator)
               {
                  if (required == null)
                  {
                     required = Boolean.TRUE;
                  }
               }
               else
               {
                  return null;
               }
            }

            parent = parent.getParent();
         }

         return required;
      }
   };

   /**
    * Visitor for normalizing the where clauses.
    */
   private final static Visitor NORMALIZATION_VISITOR = new Visitor()
   {
      public boolean visit(Query query)
      {
         query.normalizeWhere(false);
         query.normalizeRequired();

         return true;
      }

      public boolean postVisit(Query query)
      {
         return true;
      }

      public boolean isEligible(Query query)
      {
         return true;
      }
   };

   /**
    * Visitor for resolving caching and heterogeneous queries.
    */
   private final static Visitor PLANNING_VISITOR = new Visitor()
   {
      public boolean visit(Query query)
      {
         if (query.isRoot())
         {
            query.addRoot(query);
         }

         return true;
      }

      public boolean postVisit(Query query)
      {
         planCaching(query);

         // Heterogeneous joins must be output if the child node provides object identity
         query.setOutput(query.isJoin() && query.isLazy() &&
            !((ClassMapping)query.getAttributeMapping()).isInner());

         return true;
      }

      private void planCaching(Query query)
      {
         if (!query.isCachingSpecified() && !query.isLocking() && !query.getRoot().isAggregate() &&
            (query.getPersistenceMapping().getCaching() == PersistenceMapping.CACHING_CLASS ||
             query.getPersistenceMapping().getCaching() == PersistenceMapping.CACHING_INSTANCE &&
             (query.getAttribute() != null && !query.isCollection() || query.isUnique()) &&
             (query.getSecurity() == SEC_NONE || query.getMetaclass().getReadAccessAttribute() == null ||
              query.getOID() != null)) &&
            (query.getRestriction() & (RESTRICTION_WHERE | RESTRICTION_ORDERBY)) == 0 &&
            query.getQuantorCount() == 0)
         {
            for (Iterator itr = query.getAssocIterator(ASSOC_WHERE); itr.hasNext();)
            {
               Query assoc = (Query)itr.next();

               if (assoc.isInverse() ||
                  assoc.getAttribute().isLazy() ||
                  assoc.getAssocCount(ASSOC_QUERY) != 0 ||
                  assoc.getAssocCount(ASSOC_WHERE) != 0 ||
                  assoc.getQuantorCount() != 0 ||
                  assoc.getFieldCount() != 0)
               {
                  return;
               }
            }

            for (Iterator itr = query.getAssocIterator(ASSOC_QUERY); itr.hasNext();)
            {
               Query assoc = (Query)itr.next();

               if (!assoc.isRoot() && (!assoc.isLazy() || assoc.isInverse()) || assoc.getAttribute().isLazy())
               {
                  return;
               }
            }

            for (Field field = query.getFirstOutputField(); field != null; field = field.getNext())
            {
               if (field.getAnnotation() != null)
               {
                  return;
               }
            }

            query.setCached(true);

            if (!query.isRoot())
            {
               query.makeRoot(query.getRoot().getInvocationContext());
               query.addRoot(query);
            }

            Metaclass metaclass = query.getMetaclass();

            for (int i = 0, n = metaclass.getInstanceAttributeCount(); i < n; ++i)
            {
               Attribute attribute = metaclass.getInstanceAttribute(i);

               if (!attribute.isLazy())
               {
                  query.addAttribute(ASSOC_QUERY, attribute, null, false, OUTPUT_EAGER);
               }
            }
         }
      }

      public boolean isEligible(Query query)
      {
         return true;
      }
   };

   /**
    * Visitor for normalizing the fields.
    */
   private final static Visitor FIELD_VISITOR = new Visitor()
   {
      public boolean visit(Query query)
      {
         query.normalizeFields(false);
         query.sort();

         return true;
      }

      public boolean postVisit(Query query)
      {
         return true;
      }

      public boolean isEligible(Query query)
      {
         return true;
      }
   };

   /**
    * Visitor for computing the identity queries.
    */
   private final static Visitor IDENTITY_VISITOR = new Visitor()
   {
      public boolean visit(Query query)
      {
         if (!query.isGroupedBy())
         {
            Query parent = query.getParent();

            if (parent == null)
            {
               query.setIdentity(false);
            }
            else if (!parent.isIdentity() || query.isCollection())
            {
               if (query.isOutput())
               {
                  throw new InvalidQueryException("err.persistence.attributeGroupBy",
                     new Object[]{query.getAttribute().getName(), query.getMetaclass().getName()});
               }

               query.setIdentity(false);
            }
         }

         return true;
      }

      public boolean postVisit(Query query)
      {
         if (!query.isIdentity())
         {
            for (Field field = query.getFirstOutputField(); field != null; field = field.getNext())
            {
               if (!query.isGroupedBy(field))
               {
                  throw new InvalidQueryException("err.persistence.attributeGroupBy",
                     new Object[]{
                        (field.getAttribute() != null) ? field.getAttribute().getName() :
                           (field.getAnnotation() != null) ? field.getAnnotation() : field.toString(),
                     query.getMetaclass().getName()});
               }
            }
         }

         return true;
      }

      public boolean isEligible(Query query)
      {
         return true;
      }
   };

   /**
    * Visitor for mapping the queries.
    */
   private final static Visitor MAPPING_VISITOR = new Visitor()
   {
      public boolean visit(Query query)
      {
         if (query.isRoot())
         {
            query.getAdapter().mapQuery(query);
         }

         return true;
      }

      public boolean postVisit(Query query)
      {
         query.normalizeFields(true);
         query.normalizeWhere(true);

         return true;
      }

      public boolean isEligible(Query query)
      {
         return true;
      }
   };

   /**
    * Visitor for making all fields in an operator output.
    */
   private final static Operator.Visitor OUTPUT_VISITOR = new Operator.Visitor()
   {
      public boolean visit(Operator op)
      {
         if (op instanceof AttributeOperator)
         {
            op.getSource().output();
         }

         return true;
      }

      public boolean isEligible(Operator op)
      {
         return true;
      }
   };

   /**
    * Visitor for determining if an operator is grouped by.
    */
   protected final static Operator.Visitor GROUPEDBY_VISITOR = new Operator.Visitor()
   {
      public boolean visit(Operator op)
      {
         return !(op instanceof AggregateOperator);
      }

      public boolean isEligible(Operator op)
      {
         return true;
      }
   };

   /**
    * Comparator for sorting the field map keys.
    */
   private final static Comparator FIELD_KEY_COMPARATOR = new Comparator()
   {
      public int compare(Object left, Object right)
      {
         if (left instanceof String)
         {
            return -((Comparable)right).compareTo(left);
         }

         return ((Comparable)left).compareTo(right);
      }
   };

   /**
    * Map of operator symbol to operator factory: OperatorFactory[Symbol].
    */
   private final static Lookup s_operatorMap = new HashTab(32);

   static
   {
      s_operatorMap.put(OrOperator.SYMBOL, new MultiArgOperatorFactory()
      {
         public MultiArgOperator create(Query query)
         {
            return new OrOperator();
         }
      });

      s_operatorMap.put(AndOperator.SYMBOL, new MultiArgOperatorFactory()
      {
         public MultiArgOperator create(Query query)
         {
            return new AndOperator();
         }
      });

      s_operatorMap.put(EqualsOperator.SYMBOL, new ComparisonOperatorFactory()
      {
         public ComparisonOperator create(Query query)
         {
            return new EqualsOperator();
         }
      });

      s_operatorMap.put(NotEqualsOperator.SYMBOL, new ComparisonOperatorFactory()
      {
         public ComparisonOperator create(Query query)
         {
            return new NotEqualsOperator();
         }

         public MultiArgOperator createLogical(Query query)
         {
            return new OrOperator();
         }
      });

      s_operatorMap.put(GreaterThanOperator.SYMBOL, new ComparisonOperatorFactory()
      {
         public ComparisonOperator create(Query query)
         {
            return new GreaterThanOperator();
         }
      });

      s_operatorMap.put(GreaterThanOrEqualsOperator.SYMBOL, new ComparisonOperatorFactory()
      {
         public ComparisonOperator create(Query query)
         {
            return new GreaterThanOrEqualsOperator();
         }
      });

      s_operatorMap.put(LessThanOperator.SYMBOL, new ComparisonOperatorFactory()
      {
         public ComparisonOperator create(Query query)
         {
            return new LessThanOperator();
         }
      });

      s_operatorMap.put(LessThanOrEqualsOperator.SYMBOL, new ComparisonOperatorFactory()
      {
         public ComparisonOperator create(Query query)
         {
            return new LessThanOrEqualsOperator();
         }
      });

      s_operatorMap.put(LikeOperator.SYMBOL, new ComparisonOperatorFactory()
      {
         public ComparisonOperator create(Query query)
         {
            return new LikeOperator();
         }
      });

      s_operatorMap.put(MatchOperator.SYMBOL, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args == null || // ensure exactly 2 arguments
                !(args.getTail() instanceof Pair) ||
                args.getNext().getTail() != null)
            {
               throw new InvalidQueryException("err.persistence.matchArgCount",
                  new Object[]{Primitive.createInteger(Pair.length(args))});
            }

            Operator attributeOp = query.createOperator(key, args.getHead(), nOutput);

            if (!(attributeOp instanceof AttributeOperator))
            {
               if (attributeOp == null)
               {
                  return null;
               }

               throw new InvalidQueryException("err.persistence.invalidMatchArgument",
                                               new Object[] {attributeOp});
            }

            Operator expressionOp = query.createOperator(key, args.getNext().getHead(), nOutput); // unquote

            if (!(expressionOp instanceof ConstantOperator))
            {
               if (expressionOp == null)
               {
                  return null;
               }

               throw new InvalidQueryException("err.persistence.invalidMatchArgument",
                                               new Object[] {expressionOp});
            }

            Object expression = expressionOp.getValue(); // can only set after addOperator()

            if (expression instanceof String) // parse simplified infix notation
            {
               expression = new ExpressionParser().parse((String)expression);
            }

            MatchOperator matchOp = new MatchOperator(query.getInvocationContext());

            matchOp.setAttribute((AttributeOperator)attributeOp);
            matchOp.setExpression(MatchNode.parse(expression));
            query.m_bMatch = true;

            return matchOp;
         }
      });

      s_operatorMap.put(InOperator.SYMBOL, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args == null)
            {
               throw new InvalidQueryException("err.persistence.inOperatorArgCount");
            }

            MultiArgOperator op = new InOperator();

            for (; args != null; args = args.getNext())
            {
               Operator arg = query.createOperator(key, args.getHead(), nOutput);

               if (arg == null)
               {
                  return null;
               }

               if (arg.getOrdinal() == ConstantOperator.ORDINAL)
               {
                  Object value = arg.getValue();

                  if (value instanceof Pair)
                  {
                     for (Pair pair = (Pair)value; pair != null; pair = pair.getNext())
                     {
                        if (arg == null)
                        {
                           op.addOperand(new ConstantOperator(pair.getHead()));
                        }
                        else
                        {
                           arg.setValue(pair.getHead());
                           arg.setType(Primitive.typeOf(pair.getHead()));
                           op.addOperand(arg);
                           arg = null;
                        }
                     }
                  }
                  else if (value instanceof Collection)
                  {
                     for (Iterator itr = ((Collection)value).iterator(); itr.hasNext();)
                     {
                        if (arg == null)
                        {
                           op.addOperand(new ConstantOperator(itr.next()));
                        }
                        else
                        {
                           arg.setValue(itr.next());
                           arg.setType(Primitive.typeOf(arg.getValue()));
                           op.addOperand(arg);
                           arg = null;
                        }
                     }
                  }
                  else
                  {
                     op.addOperand(arg);
                  }
               }
               else
               {
                  op.addOperand(arg);
               }
            }

            return op;
         }
      });

      s_operatorMap.put(PlusOperator.SYMBOL, new BinaryOperatorFactory()
      {
         public BinaryOperator create(Query query)
         {
            return new PlusOperator();
         }
      });

      s_operatorMap.put(MinusOperator.SYMBOL, new BinaryOperatorFactory()
      {
         public BinaryOperator create(Query query)
         {
            return new MinusOperator();
         }

         public UnaryOperator create(Object arg, Query query, Object key, byte nOutput)
         {
            UnaryOperator op = new NegationOperator();

            op.setOperand(query.createOperator(key, arg, nOutput));

            return op;
         }
      });

      s_operatorMap.put(MultiplicationOperator.SYMBOL, new BinaryOperatorFactory()
      {
         public BinaryOperator create(Query query)
         {
            return new MultiplicationOperator();
         }
      });

      s_operatorMap.put(DivisionOperator.SYMBOL, new BinaryOperatorFactory()
      {
         public BinaryOperator create(Query query)
         {
            return new DivisionOperator();
         }
      });

      s_operatorMap.put(NotOperator.SYMBOL, new UnaryOperatorFactory()
      {
         public UnaryOperator create(Query query)
         {
            return new NotOperator();
         }
      });

      s_operatorMap.put(TypeConversionOperator.SYMBOL, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args == null || args.getTail() == null)
            {
               throw new InvalidQueryException("err.persistence.castOperatorArgCount");
            }

            Symbol symbol = (Symbol)args.getHead();

            args = args.getNext();

            if (args.getTail() != null)
            {
               throw new InvalidQueryException("err.persistence.castOperatorArgCount");
            }

            return new TypeConversionOperator(Primitive.parse(symbol.getName()),
               query.createOperator(key, args.getHead(), nOutput));
         }
      });

      s_operatorMap.put(AttributeOperator.SYMBOL, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args == null)
            {
               return new AttributeOperator(query);
            }

            Metaclass metaclass = query.getMetaclass();

            if (args.getHead() instanceof Pair)
            {
               Metaclass derived = metaclass.getCastMetaclass((Pair)args.getHead());

               if (derived == null)
               {
                  throw new InvalidQueryException("err.persistence.invalidTypeCastFilter");
               }

               if (!isCompatible(derived, metaclass))
               {
                  return new ConstantOperator(null);
               }

               metaclass = derived;
               args = args.getNext();
            }

            if (args == null)
            {
               return new AttributeOperator(query);
            }

            for (;;)
            {
               Pair next = args.getNext();
               Object filter = null;

               if (next != null)
               {
                  if (next.getHead() instanceof Pair)
                  {
                     filter = next.getHead();
                     next = next.getNext();
                  }
               }

               Metaclass derived = metaclass.getDerived(query.getFilter());

               if (!isCompatible(derived, metaclass))
               {
                  return new ConstantOperator(null);
               }

               String sName = ((Symbol)args.getHead()).getName();
               Attribute attribute;

               if (nOutput == OUTPUT_NONE)
               {
                  attribute = derived.getAttribute(sName);
               }
               else
               {
                  attribute = derived.findAttribute(sName);

                  if (attribute == null)
                  {
                     return null;
                  }

                  AttributeMapping mapping = attribute.findPersistenceMapping(query.getPersistenceMapping(), false);

                  if (mapping instanceof ClassMapping &&
                     (((ClassMapping)mapping).getPersistenceMapping().getCaching() != PersistenceMapping.CACHING_NONE ||
                        query.getPersistenceMapping().getCaching() != PersistenceMapping.CACHING_NONE))
                  {
                     return null;
                  }
               }

               if (next == null)
               {
                  if (attribute.getValue() != Undefined.VALUE && !attribute.isPersistent())
                  {
                     return query.createOperator(key, attribute.getDispatchedValue(), nOutput);
                  }

                  Source source = query.addAttribute(key, attribute, filter, false, nOutput);

                  if (source == null)
                  {
                     return null;
                  }

                  return new AttributeOperator(source);
               }

               Source source = query.addAttribute(key, attribute, filter, false, nOutput);

               if (source instanceof Query)
               {
                  query = (Query)source;
                  metaclass = query.getMetaclass();
                  key = ASSOC_QUERY;
               }
               else if (source instanceof Field && ((Field)source).getOperator() == null)
               {
                  throw new InvalidQueryException("err.persistence.primitiveAssociation",
                     new Object[]{attribute.getName()});
               }
               else
               {
                  return null;
               }

               args = next;
            }
         }
      });

      s_operatorMap.put(Symbol.ATAT, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args == null)
            {
               throw new InvalidQueryException("err.persistence.missingReverseAssocClass");
            }

            if (nOutput != OUTPUT_NONE)
            {
               return null;
            }

            Metaclass metaclass = query.getMetaclass().getMetadata().getMetaclass(((Symbol)args.getHead()).getName());

            query = addAttribute(metaclass, null, args.getNext(), query, key, nOutput);

            if (query == null)
            {
               return new ConstantOperator(null);
            }

            return new AttributeOperator(query);
         }

         private Query addAttribute(Metaclass metaclass, Object where, Pair assoc, Query query, Object key, byte nOutput)
         {
            if (assoc == null)
            {
               if (!metaclass.isUpcast(query.getMetaclass()) && !query.getMetaclass().isUpcast(metaclass))
               {
                  throw new InvalidQueryException("err.persistence.reverseAssocClassMismatch",
                     new Object[]{metaclass.getName(), query.getMetaclass().getName()});
               }

               query.andWhere(where);

               return query;
            }

            Pair next = assoc.getNext();
            Object filter = null;

            if (next != null && next.getHead() instanceof Pair)
            {
               filter = next.getHead();
               next = next.getNext();
            }

            Attribute attribute = metaclass.getAttribute(((Symbol)assoc.getHead()).getName());

            if (attribute.getType().isPrimitive())
            {
               throw new InvalidQueryException("err.persistence.primitiveReverseAssoc",
                  new Object[]{attribute.getName(), metaclass.getName()});
            }

            Attribute reverse = attribute.getReverse();
            boolean bInverse = (reverse == null || !reverse.isPersistent() && attribute.isPersistent());
            Metaclass type = (Metaclass)attribute.getType();
            Metaclass derived = type.getDerived(filter);

            if (!isCompatible(derived, type))
            {
               return null;
            }

            query = addAttribute(derived, (bInverse) ? null : Pair.commutative(Symbol.AND, attribute.getWhere(), filter),
               next, query, key, nOutput);

            if (query != null)
            {
               query = (Query)query.addAttribute((next == null) ? key : ASSOC_QUERY,
                  (bInverse) ? attribute : reverse, (bInverse) ? Pair.commutative(Symbol.AND, where, filter) : where,
                  bInverse, nOutput);
            }

            return query;
         }
      });

      s_operatorMap.put(IfOperator.SYMBOL, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args != null)
            {
               Object cond = args.getHead();
               args = args.getNext();

               if (args != null)
               {
                  Object tbranch = args.getHead();
                  args = args.getNext();

                  if (args == null || args.getTail() == null)
                  {
                     Object fbranch = (args == null) ? null : args.getHead();
                     Machine machine = query.getInvocationContext().getMachine();

                     if (machine.isEvalSupported(cond))
                     {
                        cond = machine.eval(cond);
                     }
                     else
                     {
                        Operator op = query.createOperator(key, cond, nOutput);

                        if (op == null)
                        {
                           return null;
                        }

                        op.normalize(0);

                        if (!op.isConstant())
                        {
                           IfOperator iop = new IfOperator();

                           iop.setCondition(op);

                           Metaclass metaclass = query.getMetaclass();

                           query.setMetaclass(metaclass.getDerived(cond));

                           try
                           {
                              if (!iop.setThen(query.createOperator(key, tbranch, nOutput)))
                              {
                                 return null;
                              }
                           }
                           finally
                           {
                              query.setMetaclass(metaclass);
                           }

                           if (!iop.setElse(query.createOperator(key, fbranch, nOutput)))
                           {
                              return null;
                           }

                           return iop;
                        }

                        cond = op.getValue();
                     }

                     return query.createOperator(key, (Intrinsic.isTrue(cond)) ? tbranch : fbranch, nOutput);
                  }
               }
            }

            throw new InvalidQueryException("err.persistence.ifOperatorArgCount");
         }
      });

      s_operatorMap.put(Symbol.NULL_P, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args == null || args.getTail() != null)
            {
               throw new InvalidQueryException("err.persistence.unaryOperatorArgCount",
                  new Object[]{Symbol.NULL_P});
            }

            BinaryOperator op = new EqualsOperator();

            if (!op.setLeft(query.createOperator(key, args.getHead(), nOutput)))
            {
               return null;
            }

            op.setRight(new ConstantOperator(null));

            return op;
         }
      });

      s_operatorMap.put(Symbol.INSTANCE_P, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args == null || args.getTail() == null)
            {
               throw new InvalidQueryException("err.persistence.instanceOfArgCount");
            }

            Operator op = query.createOperator(key, args.getHead(), nOutput);

            if (op == null)
            {
               return null;
            }

            args = args.getNext();

            if (args.getTail() != null)
            {
               throw new InvalidQueryException("err.persistence.instanceOfArgCount");
            }

            if (op.getOrdinal() != AttributeOperator.ORDINAL || !(args.getHead() instanceof Symbol))
            {
               throw new TypeMismatchException(Symbol.INSTANCE_P);
            }

            Metaclass metaclass = query.getMetaclass().getMetadata().getMetaclass(args.getHead().toString());
            Source source = ((AttributeOperator)op).getSource();

            if (!(source instanceof Query))
            {
               throw new TypeMismatchException(Symbol.INSTANCE_P);
            }

            Query target = (Query)source;

            if (metaclass.isUpcast(target.getMetaclass()))
            {
               return new ConstantOperator(Boolean.TRUE);
            }

            if (!target.getMetaclass().isUpcast(metaclass) || metaclass.getPersistenceMapping() == null)
            {
               return new ConstantOperator(Boolean.FALSE);
            }

            if (target.getPersistenceMapping().getTypeCodeAttribute() == null ||
               metaclass.getPersistenceMapping().getTypeCodeAttribute() == null)
            {
               throw new InvalidQueryException("err.persistence.instanceOfTypeCode");
            }

            return target.addTypeCodeComparison(metaclass.getPersistenceMapping(), null);
         }
      });

      s_operatorMap.put(Symbol.ANY, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args == null || args.getTail() != null)
            {
               throw new InvalidQueryException("err.persistence.anyArgCount");
            }

            AnyOperator any = new AnyOperator(query);
            Operator op = query.createOperator(any, args.getHead(),
               (nOutput == OUTPUT_UNKNOWN) ? OUTPUT_UNKNOWN : OUTPUT_NONE);

            if (op == null)
            {
               return null;
            }

            Query[] queryArray = query.findAssocs(any);

            any.setOperand(op);

            if (queryArray != null)
            {
               for (int i = 0; i < queryArray.length; ++i)
               {
                  if (queryArray[i].isPlural())
                  {
                     return any;
                  }
               }
            }

            query.setAssocs(any, query.addAssocs(ASSOC_WHERE, queryArray, true, true));

            if (op.getType() != Primitive.BOOLEAN)
            {
               NotEqualsOperator ne = new NotEqualsOperator();

               ne.setLeft(op);
               ne.setRight(new ConstantOperator(null));

               return ne;
            }

            return op;
         }
      });

      s_operatorMap.put(Symbol.FOLD, new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args != null)
            {
               Object aspect = args.getHead();

               if (!(aspect instanceof Symbol))
               {
                  throw new TypeMismatchException(Symbol.FOLD);
               }

               args = args.getNext();

               if (args != null && args.getTail() == null)
               {
                  if (!query.isRoot() &&
                     ((ClassMapping)query.getAttributeMapping()).isPure() &&
                     query.getParent().getMetaclass().hasAspect(
                        query.getInvocationContext().getMetadata().getClassAspect(aspect.toString())))
                  {
                     return new ConstantOperator(Boolean.TRUE);
                  }

                  return query.createOperator(key, args.getHead(), nOutput);
               }
            }

            throw new InvalidQueryException("err.persistence.foldOperatorArgCount");
         }
      });

      s_operatorMap.put(Symbol.define("query?"), new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args != null)
            {
               throw new InvalidQueryException("err.persistence.operatorArgCount",
                  new Object[]{"query?", Primitive.ZERO_INTEGER});
            }

            return new ConstantOperator(Boolean.TRUE);
         }
      });

      s_operatorMap.put(Symbol.define("query-root?"), new OperatorFactory()
      {
         public Operator create(Pair args, Query query, Object key, byte nOutput)
         {
            if (args != null)
            {
               throw new InvalidQueryException("err.persistence.operatorArgCount",
                  new Object[]{"query-root?", Primitive.ZERO_INTEGER});
            }

            return new ConstantOperator(Boolean.valueOf(query.getParent() == null));
         }
      });

      s_operatorMap.put(Symbol.SUBSTRING, new IntrinsicFunctionOperatorFactory(Intrinsic.SUBSTRING,
         Primitive.STRING, new Primitive[]{Primitive.STRING, Primitive.INTEGER, Primitive.INTEGER}));

      s_operatorMap.put(Symbol.STRING_LENGTH, new IntrinsicFunctionOperatorFactory(Intrinsic.STRING_LENGTH,
         Primitive.INTEGER, new Primitive[]{Primitive.STRING}));

      s_operatorMap.put(Symbol.UNIQUE, new AggregateFunctionOperatorFactory()
      {
         public AggregateOperator create(Query query)
         {
            return new UniqueOperator(query);
         }
      });

      s_operatorMap.put(Symbol.COUNT, new AggregateFunctionOperatorFactory()
      {
         public AggregateOperator create(Query query)
         {
            return new CountOperator(query);
         }
      });

      s_operatorMap.put(Symbol.SUM, new AggregateFunctionOperatorFactory()
      {
         public AggregateOperator create(Query query)
         {
            return new SumOperator(query);
         }
      });

      s_operatorMap.put(Symbol.AVERAGE, new AggregateFunctionOperatorFactory()
      {
         public AggregateOperator create(Query query)
         {
            return new NumericAggregateOperator(Symbol.AVERAGE, query);
         }
      });

      s_operatorMap.put(Symbol.MINIMUM, new AggregateFunctionOperatorFactory()
      {
         public AggregateOperator create(Query query)
         {
            return new LookupAggregateOperator(Symbol.MINIMUM, query);
         }
      });

      s_operatorMap.put(Symbol.MAXIMUM, new AggregateFunctionOperatorFactory()
      {
         public AggregateOperator create(Query query)
         {
            return new LookupAggregateOperator(Symbol.MAXIMUM, query);
         }
      });
   }

   /**
    * The class logger.
    */
   private final static Logger s_logger = Logger.getLogger(Query.class);

   // constructors

   /**
    * Creates a root query.
    * @param metaclass The class associated with the query.
    * @param context The invocation context.
    */
   public Query(Metaclass metaclass, InvocationContext context)
   {
      assert metaclass != null;

      m_metaclass = metaclass;
      m_attribute = null;
      makeRoot(context);
      setPersistenceMapping(metaclass.getPersistenceMapping());

      if (m_persistenceMapping == null)
      {
         throw new InvalidQueryException("err.persistence.unmappedClass",
            new Object[]{metaclass.getName()});
      }

      makeRoot(context);
   }

   /**
    * Creates an associated query.
    * @param attribute The association attribute mapping.
    * @param filter The association filter.
    * @param bInverse True if the inverse attribute mapping is used.
    * @param bRequired True if the query node is required.
    */
   public Query(AttributeMapping mapping, Object filter, boolean bInverse, boolean bRequired)
   {
      m_bInverse = bInverse;
      m_bRequired = bRequired && filter == null;
      m_attribute = mapping.getAttribute();
      m_attributeMapping = mapping;
      m_filter = filter;

      assert !m_attribute.getType().isPrimitive();

      m_metaclass = (bInverse) ? m_attribute.getMetaclass() : (Metaclass)m_attribute.getType();
   }

   // operations

   /**
    * Sets the query node metaclass.
    * @param metaclass The query node metaclass to set.
    */
   public void setMetaclass(Metaclass metaclass)
   {
      m_metaclass = metaclass;
   }

   /**
    * @return The query node metaclass.
    */
   public Metaclass getMetaclass()
   {
      return m_metaclass;
   }

   /**
    * Sets the data source fragment.
    * @param sName The name of data source fragment to set.
    */
   public void setFragmentName(String sName)
   {
      if (m_persistenceMapping == null)
      {
         m_fragment = null;
      }
      else
      {
         if (sName == null)
         {
            sName = getInvocationContext().getUnitOfWork().getFragmentName(
               m_persistenceMapping.getDataSource().getFragmentCount() != 1);
         }

         m_fragment = m_persistenceMapping.getDataSource().getFragment(sName);
      }
   }

   /**
    * Sets the data source fragment.
    * @param fragment The data source fragment to set.
    */
   public void setFragment(DataSourceFragment fragment)
   {
      m_fragment = fragment;
   }

   /**
    * @return The data source fragment.
    */
   public DataSourceFragment getFragment()
   {
      return m_root.m_fragment;
   }

   /**
    * Sets the persistence mapping for this node.
    * @param mapping The mapping to set.
    */
   public void setPersistenceMapping(PersistenceMapping mapping)
   {
      m_persistenceMapping = mapping;

      if (m_persistenceMapping != null && m_persistenceMapping.isDynamic())
      {
         ((PersistenceResolver)m_persistenceMapping.getDataSource().getComponent()
            .getInstance(m_context)).resolve(this);
      }

      if (m_fragment == null)
      {
         setFragmentName((String)null);
      }
   }

   /**
    * @return The persistence mapping for this node.
    */
   public PersistenceMapping getPersistenceMapping()
   {
      return m_persistenceMapping;
   }

   /**
    * Get the parent (source) or child (destination) mapping key.
    * @see ClassMapping#getKey(boolean)
    */
   public Key getKey(boolean bChild)
   {
      assert m_parent != null;

      return ((ClassMapping)m_attributeMapping).getKey(bChild ^ m_bInverse);
   }

   /**
    * @see nexj.core.persistence.Source#getQuery()
    */
   public Query getQuery()
   {
      return this;
   }

   /**
    * @see nexj.core.persistence.Source#getType()
    */
   public Type getType()
   {
      return m_metaclass;
   }

   /**
    * @return The original query, of which this one is an alias. 
    */
   protected Query deref()
   {
      Query query = this;

      while (query.m_mapping instanceof Query)
      {
         query = (Query)query.m_mapping;
      }

      return query;
   }

   /**
    * Sets the field item.
    * @param fieldItem The field item to set.
    */
   public void setFieldItem(Object fieldItem)
   {
      deref().m_fieldItem = fieldItem;
   }

   /**
    * @return The field item.
    */
   public Object getFieldItem()
   {
      return deref().m_fieldItem;
   }

   /**
    * Sets the parent persistence mapping item.
    *
    * In heterogeneous joins, used to determine the source key of the join. The
    * returned item can be used to retrieve the value of the key from the parent query.
    *
    * In homogeneous joins, used to determine the object key of the instance retrieved
    * by this query.
    *
    * @param parentItem The parent persistence mapping item to set.
    */
   public void setParentItem(Object parentItem)
   {
      deref().m_parentItem = parentItem;
   }

   /**
    * Gets the parent persistence mapping item.
    * @return The parent persistence mapping item.
    * @see setParentItem(Object)
    */
   public Object getParentItem()
   {
      return deref().m_parentItem;
   }

   /**
    * Sets the child persistence mapping item.
    *
    * In heterogeneous joins, used to determine the destination key of the join. The
    * returned item can be used to retrieve the value of the key from this query.
    *
    * The child persistence mapping item is not used in homogeneous joins.
    *
    * @param childItem The child persistence mapping item to set.
    */
   public void setChildItem(Object childItem)
   {
      deref().m_childItem = childItem;
   }

   /**
    * Gets the child persistence mapping item. Used only in heterogeneous joins.
    * @return The child persistence mapping item.
    * @see setChildItem(Object)
    */
   public Object getChildItem()
   {
      return deref().m_childItem;
   }

   /**
    * Sets the association attribute.
    * @param attribute The association attribute to set.
    */
   public void setAttribute(Attribute attribute)
   {
      super.setAttribute(attribute);

      if (attribute != null)
      {
         assert !attribute.getType().isPrimitive();

         m_metaclass = (m_bInverse) ? attribute.getMetaclass() : (Metaclass)attribute.getType();
      }
   }

   /**
    * The association filter. Can be null.
    */
   public Object getFilter()
   {
      return m_filter;
   }

   /**
    * @see Source#isInverse()
    */
   public boolean isInverse()
   {
      return m_bInverse;
   }

   /**
    * Sets the query node required flag.
    * @param bRequired The required flag to set.
    */
   public void setRequired(boolean bRequired)
   {
      m_bRequired = bRequired;
   }

   /**
    * @return True if the query node is required.
    */
   public boolean isRequired()
   {
      return m_bRequired;
   }

   /**
    * Sets the security mode, one of the SEC_* constants.
    * @param nSecurity The security mode, one of the SEC_* constants to set.
    */
   public void setSecurity(byte nSecurity)
   {
      if (nSecurity < 0)
      {
         if (m_context.isSecure())
         {
            nSecurity = m_context.getQuerySecurity();

            if (nSecurity < 0)
            {
               nSecurity = SEC_NODE;
            }
         }
         else
         {
            nSecurity = SEC_NONE;
         }
      }

      m_nSecurity = nSecurity;
   }

   /**
    * @return The security mode, one of the SEC_* constants.
    */
   public byte getSecurity()
   {
      return m_nSecurity;
   }

   /**
    * @return True if the query instances are filtered with a security clause.
    */
   public boolean isSecure()
   {
      return m_nSecurity != SEC_NONE;
   }

   /**
    * @see nexj.core.persistence.Source#output()
    */
   public void output()
   {
      setOutput(OUTPUT_LAZY);
   }

   /**
    * Sets the query eager output flag.
    * @param bOutput The query eager output flag to set.
    */
   public void setOutput(boolean bOutput)
   {
      if (bOutput)
      {
         setOutput(OUTPUT_EAGER);
      }
   }

   /**
    * Sets the output mode.
    * @param nOutput The output mode, one of the OUTPUT_* constants.
    */
   public void setOutput(byte nOutput)
   {
      assert nOutput != OUTPUT_UNKNOWN;

      if (nOutput > m_nOutput)
      {
         if (m_attribute == null || m_attribute.isCollection())
         {
            nOutput = OUTPUT_EAGER;
         }

         m_nOutput = nOutput;

         if (nOutput == OUTPUT_EAGER)
         {
            if (!isAggregate() || m_bGroupedBy)
            {
               Attribute attribute = m_persistenceMapping.getLockingAttribute();

               if (attribute != null)
               {
                  m_lockingField = (Field)addAttribute(ASSOC_QUERY, attribute, null, false, OUTPUT_EAGER);
               }

               if (m_persistenceMapping.isTypeCodeDispatched() ||
                  m_persistenceMapping.isTypeCodeFiltered() && !m_bTypeCodeFiltered)
               {
                  m_typeCodeField = (Field)addAttribute(ASSOC_QUERY, m_persistenceMapping.getTypeCodeAttribute(),
                     null, false, OUTPUT_EAGER);
               }
            }
         }

         if (m_parent != null)
         {
            m_parent.setOutput(OUTPUT_EAGER);
         }
      }
   }

   /**
    * @return The output mode.
    */
   protected byte getOutput()
   {
      return m_nOutput;
  

   /**
    * @return The query output flag.
    */
   public final boolean isOutput()
   {
      return m_nOutput != OUTPUT_NONE;
   }

   /**
    * @see nexj.core.persistence.Source#getOperator()
    */
   public Operator getOperator()
   {
      return null;
   }

   /**
    * @return The lazy instance query flag.
    */
   public final boolean isLazy()
   {
      return m_nOutput == OUTPUT_LAZY;
   }

   /**
    * Sets the flag indicating that the engine returns no more than the requested number of instances.
    * @param bLimited The flag indicating that the engine returns no more than the requested number of instances to set.
    */
   public void setLimited(boolean bLimited)
   {
      m_bLimited = bLimited;
   }

   /**
    * @return The flag indicating that the engine returns no more than the requested number of instances.
    */
   public final boolean isLimited()
   {
      return m_bLimited;
   }

   /**
    * @return True if the query contains subcollections with the same root.
    */
   public final boolean isPlural()
   {
      return m_bPlural;
   }

   /**
    * @return True if the query has a match operator.
    */
   public final boolean isMatch()
   {
      return m_bMatch;
   }

   /**
    * Sets the identity preserving query flag.
    * @param bIdentity The identity preserving query flag to set.
    */
   protected void setIdentity(boolean bIdentity)
   {
      m_bIdentity = bIdentity;
   }

   /**
    * @return The identity preserving query flag.
    */
   public boolean isIdentity()
   {
      return m_bIdentity;
   }

   /**
    * Sets the aggregate query flag.
    * @param bAggregate The aggregate query flag to set.
    */
   public final void setAggregate(boolean bAggregate)
   {
      if (bAggregate)
      {
         if (m_groupByArray == null)
         {
            m_groupByArray = new Operator[8];
         }
      }
      else
      {
         m_groupByArray = null;
         m_nGroupByCount = 0;
         m_having = null;
      }
   }

   /**
    * @return The aggregate query flag.
    */
   public final boolean isAggregate()
   {
      return m_groupByArray != null;
   }

   /**
    * Adds an attribute to the query.
    * It must belong to the query class.
    * @param key The association map key.
    * @param attribute The attribute to add.
    * @param filter The association filter. Can be null.
    * @param bInverse True if the inverse attribute mapping is used.
    * @param nOutput The attribute output mode, one of the OUTPUT_* constants.
    * @return The attribute source, or null if the attribute is not mapped.
    */
   public Source addAttribute(Object key, Attribute attribute, Object filter, boolean bInverse, byte nOutput)
   {
      assert attribute != null;
      assert (bInverse) ?
         m_metaclass.isUpcast(attribute.getType()) ||
         attribute.getType().isUpcast(m_metaclass) :
         m_metaclass.isUpcast(attribute.getMetaclass()) ||
         attribute.getMetaclass().isUpcast(m_metaclass);
      assert filter == null || nOutput <= OUTPUT_NONE && !attribute.getType().isPrimitive();

      if (filter != null && attribute.getType().isPrimitive())
      {
         throw new InvalidQueryException("err.persistence.primitiveDynamicDerivedAssoc",
            new Object[]{attribute.getName()});
      }

      if (nOutput > OUTPUT_NONE)
      {
         Attribute accessAttribute = m_metaclass.getReadAccessAttribute();

         if (accessAttribute != null &&
            attribute.getOrdinal() == accessAttribute.getOrdinal() &&
            attribute.isStatic() == accessAttribute.isStatic() &&
            m_nSecurity != SEC_NONE)
         {
            return null;
         }
      }

      Attribute root = attribute.getPersistenceRoot();
      AttributeMapping mapping = root.findPersistenceMapping(m_persistenceMapping, bInverse);

      if (mapping == null)
      {
         Object value = attribute.getDispatchedValue();

         if (value != Undefined.VALUE)
         {
            Field field = (Field)m_fieldMap.get(attribute);

            if (field == null)
            {
               // TODO: Optimize by pre-computing operator calculability
               Operator op = createOperator(key, value, OUTPUT_UNKNOWN);

               if (op != null)
               {
                  op = op.normalize(0);

                  if (op.isConstant())
                  {
                     return null;
                  }

                  Source source = op.getSource();

                  if (op instanceof AttributeOperator)
                  {
                     if (nOutput > OUTPUT_NONE)
                     {
                        source.output();
                     }

                     return source;
                  }

                  if (Operator.findCommonSource(source, this) != null)
                  {
                     field = addOperator(op);

                     if (field.getAttribute() == null)
                     {
                        field.setAttribute(attribute);
                     }
                     else
                     {
                        if (nOutput > OUTPUT_NONE)
                        {
                           addOutputField(new Field(field, attribute));
                        }
                     }

                     m_fieldMap.put(attribute, field);
                  }
               }
            }

            if (field != null)
            {
               if (nOutput > OUTPUT_NONE)
               {
                  addOutputField(field);
               }

               return field;
            }
         }

         if (nOutput == OUTPUT_NONE)
         {
            throw new InvalidQueryException("err.persistence.nonPersistentAttribute",
               new Object[]{attribute.getName()});
         }

         if (nOutput > OUTPUT_NONE)
         {
            addDependency(key, attribute.getMetaclass(), attribute.getMetaclass(),
               attribute.getCumulativeDependency(), OUTPUT_LAZY);
         }

         return null;
      }

      if (attribute.getType().isPrimitive())
      {
         Field field = (Field)m_fieldMap.get(root);

         if (field == null)
         {
            field = new Field(this, root);
            field.setAttributeMapping(mapping);
            m_fieldMap.put(root, field);
         }

         if (nOutput > OUTPUT_NONE)
         {
            addOutputField(field);
         }

         return field;
      }

      Query assoc;

      // Try to use the parent query with a reverse association

      if (m_attribute != null &&
         ((bInverse) ? m_bInverse && attribute.getOrdinal() == m_attribute.getOrdinal() :
         attribute.isReverseOf(m_attribute) && !attribute.isCollection()) &&
         (nOutput > OUTPUT_NONE || m_attribute.isRequired()))
      {
         assoc = m_parent;
         assoc.addRestriction(key, nOutput, 0, m_nRestriction);
      }
      else
      {
         Attribute req = (bInverse) ? attribute.getReverse() : attribute;
         boolean bRequired = (req != null && req.isRequired() &&
            req.getMetaclass().isUpcast(m_metaclass));

         assoc = findAssoc(key, root, filter, bInverse);

         if (assoc == null)
         {
            assoc = new Query(mapping, filter, bInverse, bRequired);

            if (m_nSecurity == SEC_ALL)
            {
               assoc.setSecurity(SEC_ALL);
            }

            assoc.addRestriction(key, nOutput, RESTRICTION_PARENT, m_nRestriction);
            addAssoc(key, assoc);

            if (bInverse)
            {
               andWhere(root.getWhere());
               andWhere(filter);
               assoc.setWhere(key, null);
            }
            else
            {
               assoc.setWhere(key, Pair.commutative(Symbol.AND, root.getWhere(), filter));
            }
         }
         else
         {
            assoc.addRestriction(key, nOutput, RESTRICTION_PARENT, 0);

            if (!bRequired)
            {
               assoc.setRequired(false);
            }
         }
      }

      if (nOutput > OUTPUT_NONE)
      {
         assoc.setOutput(OUTPUT_LAZY);
      }

      return assoc;
   }

   /**
    * Adds an attribute with a given name to the query.
    * @param key The association map key.
    * @param sName The attribute name.
    * @param bInverse True if the inverse attribute mapping is used.
    * @param nOutput The attribute output mode, one of the OUTPUT_* constants.
    * @return The attribute source, or null if the attribute is not mapped.
    */
   public Source addAttribute(Object key, String sName, boolean bInverse, byte nOutput)
   {
      assert sName != null;

      return addAttribute(key, m_metaclass.getAttribute(sName), null, bInverse, nOutput);
   }

   /**
    * Adds a dependency attribute list to this query: (a1 a2 (a3 a3.1 ... a3.N) ... aN).
    * @param key The association map key.
    * @param metaclass The class for the dependency attribute list.
    * @param base The base class for the dependency attribute list.
    * @param pair The dependency attribute symbol list.
    * @param nOutput Attribute output mode, one of the OUTPUT_* constants.
    */
   public void addDependency(Object key, Metaclass metaclass, Metaclass base, Pair pair, byte nOutput)
   {
      assert m_metaclass.isUpcast(metaclass);

      try
      {
         for (; pair != null; pair = pair.getNext())
         {
            assert pair.getHead() != null;

            if (pair.getHead() instanceof Pair)
            {
               Pair head = (Pair)pair.getHead();

               if (head == null || head.getHead() == null)
               {
                  throw new InvalidQueryException("err.persistence.queryDep");
               }

               Attribute attribute;
               Object operator = head.getHead();

               if (operator instanceof Symbol)
               {
                  if (operator == Symbol.ATAT)
                  {
                     Pair next = head.getNext();

                     if (next == null || !(next.getHead() instanceof Symbol))
                     {
                        throw new InvalidQueryException("err.persistence.queryPoly");
                     }

                     Metaclass subclass = metaclass.getMetadata().getMetaclass(next.getHead().toString());

                     if (!metaclass.isUpcast(subclass))
                     {
                        if (!subclass.isUpcast(metaclass))
                        {
                           if (base != metaclass && base.isUpcast(subclass))
                           {
                              continue;
                           }

                           throw new InvalidQueryException("err.persistence.querySubclass",
                              new Object[]{subclass.getName(), metaclass.getName()});
                        }

                        subclass = metaclass;
                     }

                     addDependency(key, subclass, subclass, next.getNext(), nOutput);

                     continue;
                  }

                  if (operator == Symbol.COLON)
                  {
                     Pair next = head.getNext();

                     if (next == null || !(next.getHead() instanceof Symbol))
                     {
                        throw new InvalidQueryException("err.persistence.queryAnnotation");
                     }

                     String sName = next.getHead().toString();

                     next = next.getNext();

                     if (next == null || next.getTail() != null)
                     {
                        throw new InvalidQueryException("err.persistence.queryAnnotation");
                     }

                     addAnnotation(sName, next.getHead());

                     continue;
                  }

                  attribute = metaclass.getAttribute(operator.toString());
               }
               else
               {
                  attribute = metaclass.getDerivedAttribute((Attribute)operator);
               }

               Source source = addAttribute(key, attribute, null, false, nOutput);

               if (source instanceof Query)
               {
                  Query query = (Query)source;
                  Metaclass type = (Metaclass)attribute.getType();

                  query.addDependency(key,
                     (query.getMetaclass().isUpcast(type)) ? type : query.getMetaclass(),
                     type, head.getNext(), nOutput);
               }
               else if (head.getTail() != null && attribute.getType().isPrimitive() &&
                  attribute.getType() != Primitive.ANY)
               {
                  throw new InvalidQueryException("err.persistence.queryDep");
               }
            }
            else
            {
               Attribute attribute;

               if (pair.getHead() instanceof Symbol)
               {
                  attribute = metaclass.getAttribute(pair.getHead().toString());
               }
               else
               {
                  attribute = metaclass.getDerivedAttribute((Attribute)pair.getHead());
               }

               addAttribute(key, attribute, null, false, nOutput);
            }
         }
      }
      catch (ClassCastException e)
      {
         throw new InvalidQueryException("err.persistence.queryDep", e);
      }
      catch (MetadataException e)
      {
         throw new InvalidQueryException("err.persistence.queryDep", e);
      }
   }

   /**
    * Adds an annotation to the query.
    * @param sName The annotation name.
    * @param value The annotation S-expression.
    * @return The annotation field.
    */
   public Field addAnnotation(String sName, Object value)
   {
      assert sName != null;

      Field field = (Field)m_fieldMap.get(sName);

      if (field != null)
      {
         throw new InvalidQueryException("err.persistence.queryAnnotationDup", new Object[]{sName});
      }

      Operator op = createOperator(ASSOC_QUERY, value, OUTPUT_UNKNOWN);

      if (op != null)
      {
         op = op.normalize(0);

         if (op.isConstant())
         {
            field = new Field(this, sName, op.getValue());
         }
         else
         {
            Source source = op.getSource();

            if (op instanceof AttributeOperator)
            {
               Query query = source.getQuery();

               if (source instanceof Field && query == this)
               {
                  field = (Field)source;
                  query.addOutputField(field);

                  if (field.getAnnotation() == null)
                  {
                     field.setAnnotation(sName);
                  }
                  else
                  {
                     addOutputField(new Field(field, sName));
                  }
               }
            }
            else if (Operator.findCommonSource(source, this) != null)
            {
               field = addOperator(op);

               if (field.getAnnotation() == null)
               {
                  field.setAnnotation(sName);
               }
               else
               {
                  addOutputField(new Field(field, sName));
               }
            }
         }
      }

      if (field == null)
      {
         if (op != null)
         {
            op.visit(OUTPUT_VISITOR, Operator.VISIT_PREORDER);
         }
         else
         {
            addDependency(ASSOC_QUERY, m_metaclass, m_metaclass,
               m_metaclass.dependency(value, false, getInvocationContext().getMachine()), OUTPUT_LAZY);
         }

         field = new Field(this, sName,
            new Compiler().compile(Pair.list(Symbol.LAMBDA, Pair.list(Symbol.THIS), value),
               null, m_context.getMachine(), false));
      }

      m_fieldMap.put(sName, field);
      addOutputField(field);

      return field;
   }

   /**
    * Adds an S-expression to this query: (op1 (op2 arg1 arg2 ... argN) ...)
    * @param key The association map key.
    * @param obj The object to add as an operator.
    * @param nOutput The attribute output mode, one of the OUTPUT_* constants.
    * @return The created operator node, or null if the operator is not supported.
    * The latter is possible only if nOutput != OUTPUT_NONE.
    */
   public Operator createOperator(Object key, Object obj, byte nOutput)
   {
      try
      {
         while (obj instanceof Pair)
         {
            Pair pair = (Pair)obj;

            obj = pair.getHead();

            if (obj instanceof Pair)
            {
               Pair head = (Pair)obj;

               if (head.getHead() == Symbol.GLOBAL)
               {
                  head = head.getNext();

                  if (head.getTail() == null && head.getHead() instanceof Symbol)
                  {
                     obj = head.getHead();
                  }
               }
            }

            if (obj instanceof Symbol)
            {
               Object factory = s_operatorMap.get(obj);

               if (factory != null)
               {
                  Operator op = ((OperatorFactory)factory).create(pair.getNext(), this, key, nOutput);

                  if (nOutput == OUTPUT_NONE && op == null)
                  {
                     throw new InvalidQueryException("err.persistence.unsupportedOperator",
                        new Object[]{obj.toString()});
                  }

                  return op;
               }
            }

            Machine machine = getInvocationContext().getMachine();

            if (obj instanceof Symbol)
            {
               Object value = machine.getGlobalEnvironment().findVariable((Symbol)obj);

               if (value instanceof Macro)
               {
                  obj = machine.invoke((Function)value, pair.getNext());

                  continue;
               }
            }

            if (nOutput == OUTPUT_NONE || machine.isEvalSupported(pair))
            {
               return new ConstantOperator(machine.eval(obj, pair.getNext()));
            }

            return null;
         }

         if (obj instanceof Symbol)
         {
            if (nOutput != OUTPUT_NONE)
            {
               return null;
            }

            Attribute attribute = m_metaclass.getAttribute(obj.toString());

            if (attribute.getValue() != Undefined.VALUE && !attribute.isPersistent())
            {
               return createOperator(key, attribute.getDispatchedValue(), nOutput);
            }

            return new AttributeOperator(addAttribute(key, attribute, null, false, nOutput));
         }

         return new ConstantOperator(obj);
      }
      catch (ClassCastException e)
      {
         throw new InvalidQueryException("err.persistence.queryOperator", e);
      }
      catch (MetadataException e)
      {
         throw new InvalidQueryException("err.persistence.queryOperator", e);
      }
      catch (ScriptingException e)
      {
         if (nOutput == OUTPUT_NONE)
         {
            throw new InvalidQueryException("err.persistence.unsupportedExpression");
         }

         return null;
      }
   }

   /**
    * Adds to the query an operator evaluated by the persistence engine.
    * @param op The operator to add.
    * @return The operator field.
    */
   public Field addOperator(Operator op)
   {
      assert op != null;

      Field field = (Field)m_fieldMap.get(op);

      if (field == null)
      {
         field = new Field(this, op);
         m_fieldMap.put(op, field);
      }

      return field;
   }

   /**
    * Adds a field to the output field list.
    * @param field The field to add.
    */
   protected void addOutputField(Field field)
   {
      if (!field.isOutput())
      {
         if (m_firstOutputField == null)
         {
            m_firstOutputField = m_lastOutputField = field;
         }
         else
         {
            m_lastOutputField.setNext(field);
            m_lastOutputField = field;
         }

         field.setOutput(true);
      }

      if (field.getAttribute() != null)
      {
         setOutput(OUTPUT_EAGER);
      }
   }

   /**
    * @return The field count.
    */
   public int getFieldCount()
   {
      return m_fieldMap.size();
   }

   /**
    * @return The field iterator.
    */
   public Lookup.Iterator getFieldIterator()
   {
      return m_fieldMap.valueIterator();
   }

   /**
    * @return The first output field.
    */
   public Field getFirstOutputField()
   {
      return m_firstOutputField;
   }

   /**
    * @return The type code field. Can be null.
    */
   public Field getTypeCodeField()
   {
      return m_typeCodeField;
   }

   /**
    * @return True if the where clause includes a type code filter.
    */
   public boolean isTypeCodeFiltered()
   {
      return m_bTypeCodeFiltered;
   }

   /**
    * @return The locking field. Can be null.
    */
   public Field getLockingField()
   {
      return m_lockingField;
   }

   /**
    * Makes the query node a root node.
    * @param context The invocation context.
    */
   public void makeRoot(InvocationContext context)
   {
      assert context != null;

      setRoot(this);
      m_context = context;

      if (m_persistenceMapping != null)
      {
         m_adapter = (PersistenceAdapter)m_persistenceMapping.getDataSource()
            .getComponent().getInstance(m_context);
      }
   }

   /**
    * Sets the root node of the subtree, including on associated query nodes with the same root.
    * @param root The root node of the subtree to set.
    */
   public void setRoot(final Query root)
   {
      assert root != null;

      if (root != m_root)
      {
         if (m_queryArray != null || m_whereArray != null || m_quantorMap != null)
         {
            visit(new Visitor()
            {
               public boolean visit(Query query)
               {
                  query.m_root = root;

                  return true;
               }

               public boolean postVisit(Query query)
               {
                  return true;
               }

               public boolean isEligible(Query query)
               {
                  return !query.isRoot();
               }
            }, VISIT_ALL);
         }
         else
         {
            m_root = root;
         }
      }
   }

   /**
    * @return The root node of the subtree.
    */
   public Query getRoot()
   {
      return m_root;
   }

   /**
    * @return True if this is the root node of the subtree.
    */
   public boolean isRoot()
   {
      return m_root == this;
   }

   /**
    * @return True if this is the root node of a heterogeneous query.
    */
   public boolean isJoin()
   {
      return m_root == this && m_parent != null;
   }

   /**
    * Determines if a query belong to the same subtree as this one.
    * @param query The query to check.
    * @return True if it belongs to the same subtree.
    */
   public boolean isSameRoot(Query query)
   {
      assert query != null;
      assert query.getRoot() != null;
      assert getRoot() != null;

      return query.getRoot() == getRoot();
   }

   /**
    * @return The parent query, if this query has a non-lazy mapping; otherwise this query.
    */
   private Query getInner()
   {
      if (isJoin() && !m_bInverse && !m_attribute.isLazy())
      {
         return m_parent;
      }

      return this;
   }

   /**
    * @see nexj.core.persistence.Source#findCommon(nexj.core.persistence.Source)
    */
   public Source findCommon(Source source)
   {
      Query query = source.getQuery();

      if (isSameRoot(query))
      {
         return m_root;
      }

      Query inner = getInner();

      if (inner.isSameRoot(query))
      {
         return inner.getRoot();
      }

      if (source == query)
      {
         Query other = query.getInner();

         if (inner.isSameRoot(other))
         {
            return inner.getRoot();
         }

         if (other.isSameRoot(query))
         {
            return other.getRoot();
         }
      }

      return null;
   }

   /**
    * Adds a query node to the query root list.
    * @param query The query to add.
    */
   public void addRoot(Query query)
   {
      if (m_rootList == null)
      {
         m_rootList = m_parent.m_root.m_rootList;
      }

      m_rootList.add(query);
   }

   /**
    * @return True if this query uses heterogeneous joins.
    */
   public boolean isHeterogeneous()
   {
      return m_rootList != null && m_rootList.size() > 1;
   }

   /**
    * Sets the parent query.
    * @param parent The parent query to set.
    */
   public void setParent(Query parent)
   {
      m_parent = parent;

      if (parent != null)
      {
         if (m_root == null || m_root != this)
         {
            m_root = parent.getRoot();

            if (m_nTimeout < 0)
            {
               m_nTimeout = m_root.getTimeout();
            }
         }

         m_quantorRoot = parent.getQuantorRoot();
      }
   }

   /**
    * @return The parent query.
    */
   public Query getParent()
   {
      return m_parent;
   }

   /**
    * Adds a new output query to the root.
    * @param outputQuery The output query to add.
    */
   public void addOutputQuery(Query outputQuery)
   {
      if (m_outputQueryList == null)
      {
         m_outputQueryList = new ArrayList();
      }

      outputQuery.setOrdinal(m_outputQueryList.size());
      m_outputQueryList.add(outputQuery);
   }

   /**
    * Gets a output query by ordinal number.
    * @param nOrdinal The output query ordinal number (0-based).
    * @return The output query object.
    */
   public Query getOutputQuery(int nOrdinal)
   {
      return (Query)m_outputQueryList.get(nOrdinal);
   }

   /**
    * @return The output query count.
    */
   public int getOutputQueryCount()
   {
      if (m_outputQueryList == null)
      {
         return 0;
      }

      return m_outputQueryList.size();
   }

   /**
    * @return An iterator for the contained output query objects.
    */
   public Iterator getOutputQueryIterator()
   {
      if (m_outputQueryList == null)
      {
         return EmptyIterator.getInstance();
      }

      return m_outputQueryList.iterator();
   }

   /**
    * Sets the quantor root node.
    */
   public void setQuantorRoot(Query query)
   {
      m_quantorRoot = query;
   }

   /**
    * @return The quantor root node (can be null).
    */
   public Query getQuantorRoot()
   {
      return m_quantorRoot;
   }

   /**
    * @return True if the query is a quantor root.
    */
   public boolean isQuantorRoot()
   {
      return this == m_quantorRoot;
   }

   /**
    * Returns the associations for a given key.
    * @param key The association key.
    * @return The corresponding association array, or null if not initialized yet.
    */
   protected Query[] findAssocs(Object key)
   {
      if (key == ASSOC_QUERY)
      {
         return m_queryArray;
      }

      if (key == ASSOC_WHERE)
      {
         return m_whereArray;
      }

      if (m_quantorMap != null)
      {
         return (Query[])m_quantorMap.get(key);
      }

      return null;
   }

   /**
    * Sets the associations for a given key.
    * @param key The association key.
    * @param queryArray The association array to set.
    */
   protected void setAssocs(Object key, Query[] queryArray)
   {
      if (key == ASSOC_QUERY)
      {
         m_queryArray = queryArray;
      }
      else if (key == ASSOC_WHERE)
      {
         m_whereArray = queryArray;
      }
      else if (queryArray != null)
      {
         if (m_quantorMap == null)
         {
            m_quantorMap = new HashTab(4);
         }

         m_quantorMap.put(key, queryArray);
      }
      else if (m_quantorMap != null)
      {
         m_quantorMap.remove(key);

         if (m_quantorMap.size() == 0)
         {
            m_quantorMap = null;
         }
      }
   }

   /**
    * Adds associations to the existing associations for a given key.
    * @param key The association key.
    * @param queryArray The association array to add. Can be null.
    * @param bCollection True to add the collections.
    * @param bMerge True to merge, false to alias the queries.
    * @return The new query array.
    */
   protected Query[] addAssocs(Object key, Query[] queryArray, boolean bCollection, boolean bMerge)
   {
      if (queryArray == null)
      {
         return null;
      }

      Query[] assocArray = findAssocs(key);

      if (assocArray == null)
      {
         setAssocs(key, queryArray);

         for (int i = 0; i < queryArray.length; ++i)
         {
            queryArray[i].setParent(this);
         }

         queryArray = null;
      }
      else
      {
         int nMovedCount = 0;
         int nCount = queryArray.length;

         for (int i = 0; i < nCount; ++i)
         {
            Query query = queryArray[i];
            Query assoc = find(assocArray, query.getAttribute(), query.getFilter(), query.isInverse());

            if (assoc == null)
            {
               query.setParent(this);
               assocArray = add(assocArray, query);
               queryArray[i] = null;
               ++nMovedCount;
            }
            else if (assoc.isSameRoot(query) && (bCollection || !query.isCollection()))
            {
               assoc.addQuery(query, bMerge);
            }
         }

         if (nMovedCount != 0)
         {
            setAssocs(key, assocArray);
            nCount -= nMovedCount;

            if (nCount == 0)
            {
               queryArray = null;
            }
            else
            {
               assocArray = new Query[nCount];

               for (int i = 0, k = 0; k < nCount; ++i)
               {
                  Query query = queryArray[i];

                  if (query != null)
                  {
                     assocArray[k++] = query;
                  }
               }

               queryArray = assocArray;
            }
         }
      }

      return queryArray;
   }

   /**
    * Adds the fields and associations from a given query.
    * @param query The source query; modified by the method.
    * @param bMerge True to merge, false to alias.
    */
   protected void addQuery(Query query, boolean bMerge)
   {
      assert m_attribute == query.getAttribute() &&
         m_bInverse == query.isInverse() &&
         query.getFirstOutputField() == null;

      if (bMerge)
      {
         addRestriction(query.getRestriction() & ~RESTRICTION_PARENT);

         for (Iterator itr = query.getFieldIterator(); itr.hasNext();)
         {
            Field srcField = (Field)itr.next();
            Attribute attribute = srcField.getAttribute();
            Field dstField = (Field)m_fieldMap.get(attribute);

            if (dstField == null)
            {
               srcField.setQuery(this);
               m_fieldMap.put(attribute, srcField);
            }
            else
            {
               srcField.setSource(dstField);
            }
         }
      }

      query.m_queryArray = addAssocs(ASSOC_QUERY, query.m_queryArray, true, bMerge);
      query.m_whereArray = addAssocs(ASSOC_WHERE, query.m_whereArray, true, bMerge);

      if (query.m_quantorMap != null)
      {
         for (Lookup.Iterator itr = query.m_quantorMap.iterator(); itr.hasNext();)
         {
            itr.next();

            Query[] queryArray = addAssocs(itr.getKey(), (Query[])itr.getValue(), true, bMerge);

            if (queryArray == null)
            {
               itr.remove();
            }
            else
            {
               itr.setValue(queryArray);
            }
         }
      }

      query.setSource(this);

      if (bMerge)
      {
         if (m_constraint == null)
         {
            m_constraint = query.getConstraint();
         }
      }

      normalizeWhere(false);
   }

   /**
    * Computes the association map filter key.
    * Filters are where clauses used in dynamic derived associations.
    * @param filter The association filter. Can be null.
    * @param bInverse True for inverse attribute mapping.
    * @return The filter key.
    */
   protected static Object getFilterKey(Object filter, boolean bInverse)
   {
      if (filter == null)
      {
         return Boolean.valueOf(bInverse);
      }

      if (!bInverse && !(filter instanceof Boolean))
      {
         return filter;
      }

      return new Pair(filter, Boolean.valueOf(bInverse));
   }

   /**
    * Determines if a derived class belongs to the same inheritance branch as a base class.
    * @param derived The derived class object.
    * @param base The base class object.
    * @return True if derived belongs to the same inheritance branch as base.
    */
   protected static boolean isCompatible(Metaclass derived, Metaclass base)
   {
      return base.isUpcast(derived) && derived.getPersistenceRoot() == base.getPersistenceRoot();
   }

   /**
    * Adds an associated query.
    * @param key The association key.
    * @param assoc The associated query to add.
    */
   public void addAssoc(Object key, Query assoc)
   {
      assert assoc != null;

      Attribute attribute = assoc.getAttribute();

      assert (assoc.isInverse()) ?
         m_metaclass.isUpcast(attribute.getType()) || attribute.getType().isUpcast(m_metaclass) :
         m_metaclass.isUpcast(attribute.getMetaclass()) || attribute.getMetaclass().isUpcast(m_metaclass);

      setAssocs(key, add(findAssocs(key), assoc));
      assoc.setParent(this);

      if (key instanceof Quantor)
      {
         assoc.setQuantorRoot(assoc);
      }

      AttributeMapping mapping = assoc.getAttributeMapping();

      if (mapping == null)
      {
         mapping = attribute.findPersistenceMapping(m_persistenceMapping, assoc.isInverse());
         assoc.setAttributeMapping(mapping);
      }

      if (mapping != null)
      {
         assoc.setPersistenceMapping((assoc.isInverse()) ?
            assoc.getMetaclass().getPersistenceMapping() :
            ((ClassMapping)mapping).getMapping());
      }

      if (assoc.getPersistenceMapping() == null)
      {
         if (key == ASSOC_WHERE)
         {
            throw new InvalidQueryException("err.persistence.calculatedWhereAssoc",
               new Object[]{assoc.getAttribute().getName(), assoc.getAttribute().getMetaclass().getName()});
         }

         assoc.makeRoot(getInvocationContext());
      }
      else if (assoc.getPersistenceMapping().getDataSource() != m_persistenceMapping.getDataSource())
      {
         assoc.makeRoot(getInvocationContext());
      }
      else if (assoc.isCollection())
      {
         Query root = assoc.getQuantorRoot();

         if (root == null)
         {
            root = m_root;
         }

         root.m_bPlural = true;
      }
   }

   /**
    * Adds a query to an array.
    * @param queryArray The original array.
    * @param query The query to add.
    * @return The new array.
    */
   protected static Query[] add(Query[] queryArray, Query query)
   {
      if (queryArray == null)
      {
         return new Query[]{query};
      }

      int nCount = queryArray.length;
      Query[] newArray = new Query[nCount + 1];

      System.arraycopy(queryArray, 0, newArray, 0, nCount);
      newArray[nCount] = query;

      return newArray;
   }

   /**
    * Finds a query in an array.
    * @param queryArray The array to search. Can be null.
    * @param attribute The query attribute.
    * @param filter The query filter.
    * @param bInverse True to use inverse attribute mapping.
    * @return The found query, or null if not found.
    */
   protected static Query find(Query[] queryArray, Attribute attribute, Object filter, boolean bInverse)
   {
      if (queryArray != null)
      {
         for (int i = 0; i < queryArray.length; ++i)
         {
            Query query = queryArray[i];

            if (query.getAttribute() == attribute &&
               query.isInverse() == bInverse &&
               ObjUtil.equal(query.getFilter(), filter))
            {
               return query;
            }
         }
      }

      return null;
   }

   /**
    * Finds an associated child query by association attribute.
    * @param key The association map key.
    * @param attribute The association attribute.
    * @param filter The association filter. Can be null.
    * @param bInverse True to use the inverse attribute mapping.
    * @return The found query, or null if not found.
    */
   public Query findAssoc(Object key, Attribute attribute, Object filter, boolean bInverse)
   {
      assert attribute != null;

      return find(findAssocs(key), attribute, filter, bInverse);
   }

   /**
    * Returns the associated query iterator.
    * @param key The association map key.
    * @return The associated query iterator.
    */
   public Iterator getAssocIterator(Object key)
   {
      Query[] queryArray = findAssocs(key);

      if (queryArray == null)
      {
         return EmptyIterator.getInstance();
      }

      return new ArrayIterator(queryArray);
   }

   /**
    * Returns the associated query count.
    * @param key The association map key.
    * @return The associated query count.
    */
   public int getAssocCount(Object key)
   {
      Query[] queryArray = findAssocs(key);

      if (queryArray == null)
      {
         return 0;
      }

      return queryArray.length;
   }

   /**
    * @return The quantor node count.
    */
   public int getQuantorCount()
   {
      if (m_quantorMap == null)
      {
         return 0;
      }

      return m_quantorMap.size();
   }

   /**
    * Adds a conjunction to the where clause.
    * @param where The conjunction where clause.
    */
   public void andWhere(Object where)
   {
      if (where != null)
      {
         andWhere(createOperator(ASSOC_WHERE, where, OUTPUT_NONE));
      }
   }

   /**
    * Adds a conjunction to the where clause.
    * @param op The conjunction where clause operator.
    */
   public void andWhere(Operator op)
   {
      if (op != null && (!op.isConstant() || Boolean.FALSE.equals(op.getValue())))
      {
         // addOperator() might modify m_where, hence it is invoked beforehand
         m_where = AndOperator.conjunction(m_where, op);

         if (m_nUnique == 0)
         {
            m_nUnique = -1;
         }
      }
   }

   /**
    * Collects the equality constraints in a logical expression.
    * @param op The logical expression.
    * @param sourceCollection The collection of sources. Can be null.
    * @return True if unique instance selection has been defined.
    */
   protected boolean addConstraints(Operator op, Collection sourceCollection)
   {
      Source source = null;
      Object value = null;

      switch (op.getOrdinal())
      {
         case AndOperator.ORDINAL:
            AndOperator and = (AndOperator)op;

            for (int i = 0, nCount = and.getOperandCount(); i < nCount; ++i)
            {
               if (addConstraints(and.getOperand(i), sourceCollection))
               {
                  return true;
               }
            }

            break;

         case EqualsOperator.ORDINAL:
            EqualsOperator eq = (EqualsOperator)op;

            if (eq.getLeft().getOrdinal() == AttributeOperator.ORDINAL)
            {
               if (eq.getRight().isConstant())
               {
                  source = ((AttributeOperator)eq.getLeft()).getSource();
                  value = eq.getRight().getValue();
               }
            }
            else if (eq.getRight().getOrdinal() == AttributeOperator.ORDINAL)
            {
               if (eq.getLeft().isConstant())
               {
                  source = ((AttributeOperator)eq.getRight()).getSource();
                  value = eq.getLeft().getValue();
               }
            }

            break;
      }

      if (source != null && source.getQuery() == this)
      {
         if (sourceCollection != null)
         {
            sourceCollection.add(source);
         }

         if (source == this)
         {
            if (value instanceof OIDHolder)
            {
               m_oid = ((OIDHolder)value).getOID();
            }

            return true;
         }
      }

      // TODO: Handle associated queries

      return false;
   }

   /**
    * @return True if the query retrieves a unique instance.
    */
   public boolean isUnique()
   {
      if (m_nUnique < 0)
      {
         // Stop recursion
         m_nUnique = 0;
         m_oid = null;

         if (m_where != null)
         {
            List sourceList = new ArrayList(4);

            if (addConstraints(m_where, sourceList) ||
               !sourceList.isEmpty() && getAdapter().isUnique(this, sourceList))
            {
               m_nUnique = 1;
            }
         }
      }

      return m_nUnique != 0;
   }

   /**
    * @return The query instance selection OID.
    */
   public OID getOID()
   {
      return m_oid;
   }

   /**
    * @return True if the query node retrieves a collection.
    */
   private boolean isCollection()
   {
      if (m_attribute != null)
      {
         if (m_bInverse)
         {
            if (!m_attribute.isCollection())
            {
               Attribute reverse = m_attribute.getReverse();

               if (reverse != null)
               {
                  if (reverse.isCollection())
                  {
                      return true;
                  }
               }
               else if (((ClassMapping)m_attributeMapping).isInner())
               {
                  return true;
               }
            }
         }
         else if (m_attribute.isCollection())
         {
             return true;
         }
      }

      return false;
   }

   /**
    * Adds a type code comparison operator for a given persistence mapping.
    * @param mapping The persistence mapping.
    * @param privilegeSet The privilege set, or null to add all the relevant type codes.
    */
   private Operator addTypeCodeComparison(PersistenceMapping mapping, PrivilegeSet privilegeSet)
   {
      Source typeCodeField = m_typeCodeField;

      if (typeCodeField == null)
      {
         typeCodeField = addAttribute(ASSOC_WHERE, m_persistenceMapping.getTypeCodeAttribute(), null, false, OUTPUT_NONE);
      }

      Iterator itr = mapping.getTypeCodeIterator(privilegeSet);

      if (itr.hasNext())
      {
         Object value = itr.next();

         if (itr.hasNext())
         {
            InOperator in = new InOperator();

            in.addOperand(new AttributeOperator(typeCodeField));
            in.addOperand(new ConstantOperator(value));

            do
            {
               in.addOperand(new ConstantOperator(itr.next()));
            }
            while (itr.hasNext());

            return in;
         }
         else
         {
            EqualsOperator eq = new EqualsOperator();

            eq.setLeft(new AttributeOperator(typeCodeField));
            eq.setRight(new ConstantOperator(value));

            return eq;
         }
      }
      else
      {
         return new ConstantOperator(Boolean.FALSE);
      }
   }

   /**
    * Sets the query where clause as an S-expression.
    * @param where The S-expression to set.
    */
   public void setWhere(Object where)
   {
      setWhere(ASSOC_QUERY, where);
   }

   /**
    * Sets the where clause as an S-expression.
    * @param key The association map key.
    * @param where The S-expression to set.
    */
   public void setWhere(Object key, Object where)
   {
      m_where = null;
      andWhere(where);

      if (m_attribute == null || !m_attribute.isRequired() ||
         !((ClassMapping)m_attributeMapping).isUnique())
      {
         andWhere(m_metaclass.getWhere());
      }

      if (m_nSecurity != SEC_NONE && key == ASSOC_QUERY)
      {
         Attribute attribute = m_metaclass.getReadAccessAttribute();

         if (attribute != null)
         {
            andWhere(attribute.getSymbol());
         }
      }

      if (m_persistenceMapping.isTypeCodeFiltered() &&
         (m_persistenceMapping.isTypeCodeForced() ||
            m_attribute == null ||
            m_attributeMapping.isMultiplexed()) &&
          !isUnique() ||
          m_nSecurity != SEC_NONE &&
          m_persistenceMapping.isTypeCodePrivileged(
             getInvocationContext().getPrivilegeSet()))
      {
         Operator op = addTypeCodeComparison(m_persistenceMapping,
            (m_nSecurity != SEC_NONE) ? getInvocationContext().getPrivilegeSet() : null);

         // addOperator() might modify m_where, hence it is invoked beforehand
         m_where = AndOperator.conjunction(m_where, op);
         m_bTypeCodeFiltered = true;

         if (m_nUnique == 0)
         {
            m_nUnique = -1;
         }
      }
   }

   /**
    * Sets the where clause.
    * @param where The where clause to set.
    */
   public void setWhere(Operator where)
   {
      m_where = where;
   }

   /**
    * @return The where clause.
    */
   public Operator getWhere()
   {
      return m_where;
   }

   /**
    * Sets the having clause as an S-expression.
    * @param where The S-expression to set.
    */
   public void setHaving(Object having)
   {
      setHaving((having == null) ? null : createOperator(ASSOC_QUERY, having, OUTPUT_NONE));
   }

   /**
    * Sets the having clause.
    * @param having The having clause to set.
    */
   public void setHaving(Operator having)
   {
      m_having = (having != null && (!having.isConstant() || Boolean.FALSE.equals(having.getValue()))) ? having : null;
      setAggregate(true);
   }

   /**
    * @return The having clause.
    */
   public Operator getHaving()
   {
      return m_having;
   }

   /**
    * Adds a group by clause to this query: (expr1 ... exprN).
    * @param groupBy The list of group by S-expressions.
    */
   public void addGroupBy(Pair groupBy)
   {
      try
      {
         for (; groupBy != null; groupBy = groupBy.getNext())
         {
            addGroupBy(createOperator(ASSOC_QUERY, groupBy.getHead(), OUTPUT_NONE));
         }
      }
      catch (ClassCastException e)
      {
         throw new InvalidQueryException("err.persistence.queryGroupBy", e);
      }
      catch (MetadataException e)
      {
         throw new InvalidQueryException("err.persistence.queryGroupBy", e);
      }
   }

   /**
    * Adds a group by expression.
    * @param nOrdinal The ordinal number of the expression.
    * @param operator The expression by which to group by.
    * @return The ordinal number of the added expression.
    */
   public int addGroupBy(int nOrdinal, Operator operator)
   {
      assert nOrdinal >= 0 && nOrdinal <= m_nGroupByCount;
      assert operator != null;

      if (m_groupByArray == null)
      {
         m_groupByArray = new Operator[8];
      }
      else
      {
         Operator[] groupByArray;

         if (m_nGroupByCount == m_groupByArray.length)
         {
            groupByArray = new Operator[m_nGroupByCount << 1];
            System.arraycopy(m_groupByArray, 0, groupByArray, 0, nOrdinal);
            System.arraycopy(m_groupByArray, nOrdinal, groupByArray, nOrdinal + 1, m_nGroupByCount - nOrdinal);
            m_orderByArray = groupByArray;
         }
         else
         {
            System.arraycopy(m_groupByArray, nOrdinal, m_groupByArray, nOrdinal + 1, m_nGroupByCount - nOrdinal);
         }
      }

      m_groupByArray[nOrdinal] = operator;
      m_nGroupByCount++;

      if (operator instanceof AttributeOperator)
      {
         operator.getSource().setGroupedBy(true);
      }

      return nOrdinal;
   }

   /**
    * Adds a group by expression.
    * @param operator The expression by which to group by.
    * @return The ordinal number of the added expression.
    */
   public int addGroupBy(Operator operator)
   {
      return addGroupBy(m_nGroupByCount, operator);
   }

   /**
    * Removes a group by expression.
    * @param nOrdinal The expression ordinal number.
    */
   public void removeGroupBy(int nOrdinal)
   {
      assert nOrdinal >= 0 && nOrdinal < m_nGroupByCount;

      System.arraycopy(m_groupByArray, nOrdinal + 1, m_orderByArray, nOrdinal, m_nGroupByCount-- - nOrdinal - 1);
   }

   /**
    * Replaces a group by expression.
    * @param nOrdinal The expression ordinal number.
    * @param operator The group by expression.
    */
   public void setGroupBy(int nOrdinal, Operator operator)
   {
      assert nOrdinal >= 0 && nOrdinal < m_nGroupByCount;

      m_groupByArray[nOrdinal] = operator;
   }

   /**
    * Gets a group by expression.
    * @param nOrdinal The expression ordinal number.
    * @return The group by expression.
    */
   public Operator getGroupBy(int nOrdinal)
   {
      assert nOrdinal >= 0 && nOrdinal < m_nGroupByCount;

      return m_groupByArray[nOrdinal];
   }

   /**
    * @return The group by expression count.
    */
   public int getGroupByCount()
   {
      return m_nGroupByCount;
   }

   /**
    * Determines if a field is grouped by.
    * @param field The field to test.
    * @return True if it is grouped by.
    */
   public boolean isGroupedBy(Field field)
   {
      return !isAggregate() || field.isGroupedBy() ||
         field.getOperator() != null && isGroupedBy(field.getOperator());
   }

   /**
    * Determines if an operator is grouped by.
    * @param op The operator to test.
    * @return True if it is grouped by.
    */
   public boolean isGroupedBy(Operator op)
   {
      if (!isAggregate() || op.isConstant())
      {
         return true;
      }

      if (op instanceof AttributeOperator)
      {
         return op.getSource().isGroupedBy();
      }

      if (!op.visit(GROUPEDBY_VISITOR, Operator.VISIT_PREORDER))
      {
         return true;
      }

      for (int i = 0; i < m_nGroupByCount; ++i)
      {
         if (op.compareTo(m_groupByArray[i]) == 0)
         {
            return true;
         }
      }

      return false;
   }

   /**
    * Adds an order by clause to this query: ((op1 . #t) (op2 . #t) ... (opN . #t)).
    * @param orderBy The list of order by S-expressions and ascending sort order flags.
    */
   public void addOrderBy(Pair orderBy)
   {
      try
      {
         for (; orderBy != null; orderBy = orderBy.getNext())
         {
            Pair pair = (Pair)orderBy.getHead();

            if (pair == null || pair.getTail() == null)
            {
               throw new InvalidQueryException("err.persistence.queryOrderBy");
            }

            addOrderBy(createOperator(ASSOC_QUERY, pair.getHead(), OUTPUT_NONE), ((Boolean)pair.getTail()).booleanValue());
         }
      }
      catch (ClassCastException e)
      {
         throw new InvalidQueryException("err.persistence.queryOrderBy", e);
      }
      catch (MetadataException e)
      {
         throw new InvalidQueryException("err.persistence.queryOrderBy", e);
      }
   }

   /**
    * Adds an order by expression.
    * @param nOrdinal The ordinal number of the expression.
    * @param operator The expression by which to order by.
    * @param bAscending True if ascending sort order should be applied.
    * @return The ordinal number of the added expression.
    */
   public int addOrderBy(int nOrdinal, Operator operator, boolean bAscending)
   {
      assert nOrdinal >= 0 && nOrdinal <= m_nOrderByCount;
      assert operator != null;

      nOrdinal <<= 1;

      int nCount = (m_nOrderByCount << 1);

      if (m_orderByArray == null)
      {
         m_orderByArray = new Operator[16];
      }
      else
      {
         if (nCount == m_orderByArray.length)
         {
            Operator[] orderByArray = new Operator[nCount << 1];
            System.arraycopy(m_orderByArray, 0, orderByArray, 0, nOrdinal);
            System.arraycopy(m_orderByArray, nOrdinal, orderByArray, nOrdinal + 2, nCount - nOrdinal);
            m_orderByArray = orderByArray;
         }
         else
         {
            System.arraycopy(m_orderByArray, nOrdinal, m_orderByArray, nOrdinal + 2, nCount - nOrdinal);
         }
      }

      m_orderByArray[nOrdinal] = operator;
      m_orderByArray[nOrdinal + 1] = (bAscending) ? TRUE_OPERATOR : null;
      m_nOrderByCount++;

      return nOrdinal >> 1;
   }

   /**
    * Adds an order by expression.
    * @param operator The expression by which to order by.
    * @param bAscending True if ascending sort order should be applied.
    * @return The ordinal number of the added expression.
    */
   public int addOrderBy(Operator operator, boolean bAscending)
   {
      return addOrderBy(m_nOrderByCount, operator, bAscending);
   }

   /**
    * Removes an order by expression.
    * @param nOrdinal The expression ordinal number.
    */
   public void removeOrderBy(int nOrdinal)
   {
      assert nOrdinal >= 0 && nOrdinal < m_nOrderByCount;

      nOrdinal <<= 1;

      int nCount = (m_nOrderByCount << 1);

      System.arraycopy(m_orderByArray, nOrdinal + 2, m_orderByArray, nOrdinal, nCount - nOrdinal - 2);
      --m_nOrderByCount;
   }

   /**
    * Replaces an order by expression.
    * @param nOrdinal The expression ordinal number.
    * @param operator The order by expression.
    */
   public void setOrderByOperator(int nOrdinal, Operator operator)
   {
      assert nOrdinal >= 0 && nOrdinal < m_nOrderByCount;

      m_orderByArray[nOrdinal << 1] = operator;
   }

   /**
    * Gets an order by expression.
    * @param nOrdinal The expression ordinal number.
    * @return The order by expression.
    */
   public Operator getOrderByOperator(int nOrdinal)
   {
      assert nOrdinal >= 0 && nOrdinal < m_nOrderByCount;

      return m_orderByArray[nOrdinal << 1];
   }

   /**
    * Gets an order by expression sort order.
    * @param nOrdinal The expression ordinal number.
    * @return True is the expression is sorted ascendingly.
    */
   public boolean isOrderByAscending(int nOrdinal)
   {
      assert nOrdinal >= 0 && nOrdinal < m_nOrderByCount;

      return m_orderByArray[(nOrdinal << 1) + 1] != null;
   }

   /**
    * @return The order by expression count.
    */
   public int getOrderByCount()
   {
      return m_nOrderByCount;
   }

   /**
    * Sets the maximum instance count.
    * @param nMaxCount The maximum instance count to set.
    */
   public void setMaxCount(int nMaxCount)
   {
      assert nMaxCount >= -1;

      m_nMaxCount = nMaxCount;
   }

   /**
    * @return The maximum instance count.
    */
   public int getMaxCount()
   {
      return m_nMaxCount;
   }

   /**
    * Sets the limit of the number of instances retrieved by a single read() operation.
    * @param nLimit The limit of the number of instances retrieved by a single read()
    * operation to set (negative for unlimited, 0 for default).
    */
   public void setLimit(int nLimit)
   {
      m_nLimit = nLimit;
   }

   /**
    * @return The limit of the number of instances retrieved by a single read()
    * operation (non-positive for unlimited).
    */
   public int getLimit()
   {
      if (m_nLimit == 0)
      {
         return m_persistenceMapping.getDataSource().getReadLimit();
      }

      return m_nLimit;
   }

   /**
    * Sets the instance offset.
    * @param nOffset The instance offset to set.
    */
   public void setOffset(int nOffset)
   {
      assert nOffset >= 0;

      m_nOffset = nOffset;
   }

   /**
    * @return The instance offset.
    */
   public int getOffset()
   {
      return m_nOffset;
   }

   /**
    * Sets the query timeout in seconds.
    * @param nTimeout The query timeout in seconds to set (0 for unlimited, negative to use the default).
    */
   public void setTimeout(int nTimeout)
   {
      m_nTimeout = nTimeout;
   }

   /**
    * @return The query timeout in seconds (0 for unlimited, negative to use the default).
    */
   public int getTimeout()
   {
      return m_nTimeout;
   }

   /**
    * Sets the locking flag.
    * @param bLocking The locking flag to set.
    */
   public void setLocking(boolean bLocking)
   {
      m_bLocking = bLocking;
   }

   /**
    * @return The locking flag.
    */
   public boolean isLocking()
   {
      return m_bLocking;
   }

   /**
    * Sets the query constraint.
    * @param constraint The query constraint to set.
    */
   public void setConstraint(Operator constraint)
   {
      m_constraint = constraint;
   }

   /**
    * @return The query constraint.
    */
   public Operator getConstraint()
   {
      return m_constraint;
   }

   /**
    * Sets the query generator.
    * @param generator The query generator to set.
    */
   public void setGenerator(Object generator)
   {
      m_generator = generator;
   }

   /**
    * @return The query generator.
    */
   public Object getGenerator()
   {
      return m_generator;
   }

   /**
    * Adds restriction flags.
    * @param nRestriction The restriction flags (combination of RESTRICTION_* constants).
    */
   public void addRestriction(int nRestriction)
   {
      m_nRestriction |= nRestriction;
   }

   /**
    * Adds restriction flags based on the association key and output mode.
    * @param key The association key (ASSOC_*).
    * @param nOutput The output mode (OUTPUT_*).
    * @param nRestriction The restriction mode.
    * @param nParentRestriction The parent restriction mode.
    */
   protected void addRestriction(Object key, byte nOutput, int nRestriction, int nParentRestriction)
   {
      if (nOutput == OUTPUT_NONE)
      {
         if (key == ASSOC_WHERE || key instanceof AnyOperator ||
            (nParentRestriction & RESTRICTION_PARENT) != 0 &&
            ((nParentRestriction | m_nRestriction) & RESTRICTION_WHERE) != 0)
         {
            nRestriction |= RESTRICTION_WHERE;
         }
         else
         {
            nRestriction |= RESTRICTION_ORDERBY;
         }

         addRestriction(nRestriction);
      }
      else if ((nParentRestriction & RESTRICTION_PARENT) != 0)
      {
         addRestriction(nParentRestriction & ~RESTRICTION_PARENT);
      }
   }

   /**
    * Removes restriction flags.
    * @param nRestriction The restriction flags (combination of RESTRICTION_* constants).
    */
   public void removeRestriction(int nRestriction)
   {
      m_nRestriction &= ~nRestriction;
   }

   /**
    * @return The restriction mode (combination of RESTRICTION_* constants).
    */
   public byte getRestriction()
   {
      return m_nRestriction;
   }

   /**
    * Sets the caching flag.
    * @param bCached The caching flag to set.
    */
   public void setCached(boolean bCached)
   {
      m_nCached = (bCached) ? (byte)1 : 0;
   }

   /**
    * @return The caching flag.
    */
   public boolean isCached()
   {
      return m_nCached > 0;
   }

   /**
    * @return True if the caching flag has been specified.
    */
   public boolean isCachingSpecified()
   {
      return m_nCached >= 0;
   }

   /**
    * Sets the subquery flag (for persistence mapping purposes).
    * @param bSubquery The subquery flag to set.
    */
   public void setSubquery(boolean bSubquery)
   {
      m_bSubquery = bSubquery;
   }

   /**
    * @return The subquery flag (for persistence mapping purposes).
    */
   public boolean isSubquery()
   {
      return m_bSubquery;
   }

   /**
    * Sets the cursor retrieval flag.
    * @param bCursor The cursor retrieval flag to set.
    */
   public void setCursor(boolean bCursor)
   {
      m_bCursor = bCursor;
   }

   /**
    * @return The cursor retrieval flag.
    */
   public boolean isCursor()
   {
      return m_bCursor;
   }

   /**
    * Sets the persistence adapter on this query.
    * @param adapter The persistence adapter to set.
    */
   public void setAdapter(PersistenceAdapter adapter)
   {
      m_adapter = adapter;
   }

   /**
    * Gets the persistence adapter from the query root.
    * @return The persistence adapter.
    */
   public PersistenceAdapter getAdapter()
   {
      return m_root.m_adapter;
   }

   /**
    * Sets the invocation context.
    * @param context The invocation context to set.
    */
   public void setInvocationContext(InvocationContext context)
   {
      m_context = context;
   }

   /**
    * @return The invocation context.
    */
   public InvocationContext getInvocationContext()
   {
      return m_root.m_context;
   }

   /**
    * Reduces the number of involved where query nodes to eliminate
    * superfluous joins by aliasing them into the query map.
    */
   public void optimizeJoins()
   {
      m_whereArray = addAssocs(ASSOC_QUERY, m_whereArray, false, false);
   }

   /**
    * Reduces the query multiplicity.
    * TODO: Use metadata and include in the query planning pass.
    * @return The current multiplicity.
    */
   public int reduce()
   {
      int nMultiplicity = 1;

      for (Iterator itr = getAssocIterator(ASSOC_QUERY); itr.hasNext();)
      {
         Query query = (Query)itr.next();
         int nAssocMultiplicity = query.reduce();

         if (nAssocMultiplicity > 7 && nMultiplicity > 1)
         {
            if (query.isOutput() &&
               (query.getRestriction() & (RESTRICTION_WHERE | RESTRICTION_ORDERBY)) == 0 &&
               findAssoc(ASSOC_WHERE, query.getAttribute(), query.getFilter(), query.isInverse()) == null)
            {
               query.makeRoot(getRoot().getInvocationContext());
            }
         }

         if (nAssocMultiplicity != 0)
         {
            nMultiplicity *= nAssocMultiplicity;
         }
      }

      if (isRoot() || (getRestriction() & (RESTRICTION_WHERE | RESTRICTION_ORDERBY)) != 0)
      {
         return 0;
      }

      if (isCollection())
      {
         nMultiplicity <<= 3;
      }

      return nMultiplicity;
   }

   /**
    * @see nexj.core.persistence.Source#getConstrainedValue()
    */
   public Object getConstrainedValue()
   {
      if (m_parent != null)
      {
         return m_parent.getConstrainedValue(this);
      }

      return Undefined.VALUE;
   }

   /**
    * Gets the constant value of a given source inferred from constraints.
    * @param source The source.
    * @return The constant value, or Undefined.VALUE if not constant.
    */
   protected Object getConstrainedValue(Source source)
   {
      Operator op = m_constraint;

      if (op instanceof EqualsOperator)
      {
         EqualsOperator eq = (EqualsOperator)m_constraint;
         Object value = eq.getRight().getValue();

         if (value instanceof OIDHolder)
         {
            OID oid = ((OIDHolder)value).getOID();

            if (oid != null)
            {
               op = eq.getParent();

               while (op != null && op.getOrdinal() == AndOperator.ORDINAL)
               {
                  op = op.getParent();
               }

               if (op == null || op.getOrdinal() == AnyOperator.ORDINAL)
               {
                  return getAdapter().getValue(oid, source);
               }
            }
         }

         setConstraint(null);
      }

      return Undefined.VALUE;
   }

   /**
    * Sorts a query array.
    * @param queryArray The array to sort. Modified by this method. Can be null.
    */
   protected static void sort(Query[] queryArray)
   {
      if (queryArray != null)
      {
         // Sort the queries topologically

         if (queryArray.length > 1)
         {
            QueryDepVisitor visitor = new QueryDepVisitor(queryArray);
            boolean bDep = false;

            for (int i = 0; i < queryArray.length; ++i)
            {
               queryArray[i].m_nPredCount = 0;
            }

            for (int i = 0; i < queryArray.length; ++i)
            {
               Query query = queryArray[i];
               Query[] depArray = visitor.findDeps(query);

               if (depArray != null)
               {
                  for (int k = 0; k < depArray.length; ++k)
                  {
                     ++depArray[k].m_nPredCount;
                  }

                  bDep = true;
               }

               query.m_depArray = depArray;
            }

            if (bDep)
            {
               int nEnd = queryArray.length;
               int nStart = nEnd;
               Query[] topoArray = new Query[nEnd];

               for (int i = queryArray.length - 1; i >= 0; --i)
               {
                  Query query = queryArray[i];

                  if (query.m_nPredCount == 0)
                  {
                     topoArray[--nStart] = query;
                  }
               }

               while (nEnd > nStart)
               {
                  if (nEnd - nStart > 1)
                  {
                     Arrays.sort(topoArray, nStart, nEnd);
                  }

                  int i = nEnd;

                  nEnd = nStart;
                 
                  while (i > nEnd)
                  {
                     Query[] depArray = topoArray[--i].m_depArray;

                     if (depArray != null)
                     {
                        for (int k = 0; k < depArray.length; ++k)
                        {
                           Query query = depArray[k];

                           if (--query.m_nPredCount == 0)
                           {
                              topoArray[--nStart] = query;
                           }
                        }
                     }
                  }

               }

               if (nEnd > 0)
               {
                  for (int i = queryArray.length - 1; i >= 0; --i)
                  {
                     Query query = queryArray[i];

                     if (query.m_nPredCount != 0)
                     {
                        topoArray[--nStart] = query;
                     }
                  }
               }

               if (nEnd - nStart > 1)
               {
                  Arrays.sort(topoArray, nStart, nEnd);
               }

               System.arraycopy(topoArray, 0, queryArray, 0, topoArray.length);

               for (int i = 0; i < queryArray.length; ++i)
               {
                  queryArray[i].m_depArray = null;
               }
            }
            else
            {
               Arrays.sort(queryArray);
            }
         }
      }
   }

   /**
    * Sorts data to ensure consistent query plan caching in persistence
    * stores using generated query strings as cache keys.
    */
   public void sort()
   {
      // Sort fields

      if (m_fieldMap.size() > 1)
      {
         Object[] keyArray = new Object[m_fieldMap.size()];
         int i = 0;
        
         for (Lookup.Iterator itr = m_fieldMap.iterator(); itr.hasNext();)
         {
            keyArray[i++] = itr.next();
            ((Field)itr.getValue()).setNext(null);
         }

         Arrays.sort(keyArray, FIELD_KEY_COMPARATOR);

         Lookup fieldMap = new LinkedHashTab(keyArray.length);
         Field firstOutput = null;
         Field lastOutput = null;

         for (i = 0; i < keyArray.length; ++i)
         {
            Object key = keyArray[i];
            Field field = (Field)m_fieldMap.get(key);

            fieldMap.put(key, field);

            if (field.isOutput() && field.getNext() == null && field != lastOutput)
            {
               if (lastOutput == null)
               {
                  firstOutput = lastOutput = field;
               }
               else
               {
                  lastOutput.setNext(field);
                  lastOutput = field;
               }
            }
         }

         if (lastOutput != null)
         {
            lastOutput.setNext(null);
            m_firstOutputField = firstOutput;
            m_lastOutputField = lastOutput;
         }

         m_fieldMap = fieldMap;
      }

      // Sort associations

      sort(m_queryArray);
      sort(m_whereArray);

      if (m_quantorMap != null)
      {
         for (Iterator itr = m_quantorMap.valueIterator(); itr.hasNext();)
         {
            sort((Query[])itr.next());
         }
      }
   }

   /**
    * Normalizes the fields.
    * @param bPersistence True to normalize the persistence mappings.
    */
   public void normalizeFields(boolean bPersistence)
   {
      Field prev = null;

      for (Field field = m_firstOutputField; field != null; field = field.getNext())
      {
         Operator opSaved = field.getOperator();

         if (field.normalize((bPersistence) ? Operator.NORMALIZE_PERSISTENCE : 0))
         {
            prev = field;
         }
         else
         {
            Field next = field.getNext();

            if (prev == null)
            {
               m_firstOutputField = next;

               if (next == null)
               {
                  m_lastOutputField = null;
               }
            }
            else
            {
               prev.setNext(next);
            }

            if (opSaved != null)
            {
               m_fieldMap.remove(opSaved);
            }

            if (field.getAttribute() != null)
            {
               m_fieldMap.remove(field.getAttribute());
            }

            Operator op = field.getOperator();

            if (op != null)
            {
               op.visit(OUTPUT_VISITOR, Operator.VISIT_PREORDER);
            }
         }
      }
   }

   /**
    * Normalizes the where and having clauses.
    * @param bPersistence True to normalize the persistence mappings.
    */
   public void normalizeWhere(boolean bPersistence)
   {
      if (m_where != null)
      {
         m_where = m_where.normalize((bPersistence) ?
            Operator.NORMALIZE_WHERE | Operator.NORMALIZE_PERSISTENCE :
            Operator.NORMALIZE_WHERE);

         if (m_where.getType() != null && m_where.getType() != Primitive.BOOLEAN)
         {
            throw new InvalidQueryException("err.persistence.whereType");
         }

         if (m_where.isConstant() && !Boolean.FALSE.equals(m_where.getValue()))
         {
            m_where = null;
         }
      }

      if (bPersistence && m_having != null)
      {
         m_having = m_having.normalize(Operator.NORMALIZE_HAVING | Operator.NORMALIZE_PERSISTENCE);

         if (m_having.getType() != null && m_having.getType() != Primitive.BOOLEAN)
         {
            throw new InvalidQueryException("err.persistence.whereType");
         }

         if (m_having.isConstant() && !Boolean.FALSE.equals(m_having.getValue()))
         {
            m_having = null;
         }
      }

      if (m_where == null && m_having == null)
      {
         m_whereArray = null;

         if (m_firstOutputField == null && m_fieldMap.size() != 0 &&
            m_nOrderByCount == 0 && m_nGroupByCount == 0)
         {
            for (Lookup.Iterator itr = m_fieldMap.valueIterator(); itr.hasNext();)
            {
               Field field = (Field)itr.next();

               if (field.getConstrainedValue() != Undefined.VALUE)
               {
                  itr.remove();
               }
            }
         }
      }

      if (isJoin())
      {
         if (m_where != null && m_where.getOrdinal() != AndOperator.ORDINAL)
         {
            AndOperator and = new AndOperator();

            and.addOperand(m_where);
            m_where = and;
         }
      }
   }

   /**
    * Sets association query required flags, where possible.
    */
   private void normalizeRequired()
   {
      visit(REQUIRED_RESTRICTION_CLEANUP_VISITOR, VISIT_WHERE | VISIT_QUANTOR, VISIT_QUERY);

      if (m_where != null)
      {
         m_where.visit(REQUIRED_RESTRICTION_COMPUTING_VISITOR, Operator.VISIT_PREORDER);
      }

      if (m_quantorMap != null)
      {
         for (Iterator itr = m_quantorMap.iterator(); itr.hasNext();)
         {
            Operator op = ((Quantor)itr.next()).getOperand();

            if (op != null)
            {
               op.visit(REQUIRED_RESTRICTION_COMPUTING_VISITOR, Operator.VISIT_PREORDER);
            }
         }
      }

      visit(new Visitor()
      {
         public boolean visit(Query query)
         {
            byte nRestriction = (byte)(query.getRestriction() & (RESTRICTION_REQUIRED | RESTRICTION_NOT_REQUIRED));

            while (query != Query.this)
            {
               query = query.getParent();
               query.addRestriction(nRestriction);
            }

            return true;
         }

         public boolean postVisit(Query query)
         {
            if (query != Query.this &&
               (query.getRestriction() & (RESTRICTION_REQUIRED | RESTRICTION_NOT_REQUIRED)) == RESTRICTION_REQUIRED)
            {
               query.setRequired(true);
            }

            return true;
         }

         public boolean isEligible(Query query)
         {
            return true;
         }
      }, VISIT_WHERE | VISIT_QUANTOR, VISIT_QUERY);
   }

   /**
    * Normalizes the group by clause.
    */
   private void normalizeGroupBy()
   {
      for (int i = 0; i < m_nGroupByCount;)
      {
         Operator op = getGroupBy(i).normalize(Operator.NORMALIZE_GROUPBY);

         setGroupBy(i, op);

         if (op.isConstant())
         {
            removeGroupBy(i);
         }
         else
         {
            if (op.getOrdinal() == AttributeOperator.ORDINAL)
            {
               Source source = op.getSource();

               source.setGroupedBy(true);

               if (source.getType().isPrimitive())
               {
                  unconvert((AttributeOperator)op);
               }
               else
               {
                  Query query = source.getQuery();
                  Field[] fieldArray = query.getAdapter().getFields(query);

                  if (fieldArray != null)
                  {
                     Key key = query.getPersistenceMapping().getObjectKey();

                     for (int k = 0; k < fieldArray.length; ++k)
                     {
                        Field field = fieldArray[k];

                        field.setGroupedBy(true);

                        // No need to normalize
                        if (k == 0)
                        {
                           op.setSource(field);
                           op.setType(key.getPartType(k));
                           unconvert((AttributeOperator)op);
                        }
                        else
                        {
                           addGroupBy(++i, unconvert(new AttributeOperator(field)));
                        }
                     }
                  }
               }
            }

            ++i;
         }
      }

      if (m_having != null && !isGroupedBy(m_having))
      {
         throw new InvalidQueryException("err.persistence.ungroupedHaving");
      }
   }

   /**
    * Normalizes the order by clause.
    */
   private void normalizeOrderBy()
   {
      for (int i = 0; i < m_nOrderByCount;)
      {
         Operator op = getOrderByOperator(i).normalize(Operator.NORMALIZE_ORDERBY);

         setOrderByOperator(i, op);

         if (op.isConstant())
         {
            removeOrderBy(i);
         }
         else
         {
            if (isAggregate() && !isGroupedBy(op))
            {
               throw new InvalidQueryException("err.persistence.ungroupedOrderBy");
            }

            if (op.getOrdinal() == AttributeOperator.ORDINAL)
            {
               if (op.getSource().getType().isPrimitive())
               {
                  unconvert((AttributeOperator)op);
               }
               else
               {
                  Query query = op.getSource().getQuery();
                  Field[] fieldArray = query.getAdapter().getFields(query);

                  if (fieldArray != null)
                  {
                     Key key = query.getPersistenceMapping().getObjectKey();
                     boolean bAscending = isOrderByAscending(i) ^ key.isPartAscending(0);

                     for (int k = 0; k < fieldArray.length; ++k)
                     {
                        // No need to normalize
                        if (k == 0)
                        {
                           op.setSource(fieldArray[k]);
                           op.setType(key.getPartType(k));
                           unconvert((AttributeOperator)op);
                        }
                        else
                        {
                           addOrderBy(++i, unconvert(new AttributeOperator(fieldArray[k])),
                              bAscending ^ key.isPartAscending(k));
                        }
                     }
                  }
               }
            }

            ++i;
         }
      }
   }

   /**
    * Removes the converter from an attribute operator if it preserves the sort order.
    * @param op The attribute operator.
    * @return op.
    */
   private AttributeOperator unconvert(AttributeOperator op)
   {
      Converter converter = op.getConverter();

      if (converter != null &&
         Primitive.isOrderPreserved(converter.getSourceType(), converter.getDestinationType()))
      {
         op.setType(converter.getSourceType());
         op.setNoConversion(true);
      }

      return op;
   }

   /**
    * Prepares the query for executing.
    * @param bPartial True if the query result will be retrieved partially.
    */
   public void prepare(boolean bPartial)
   {
      if (m_parent == null)
      {
         if (bPartial && m_bPlural && (!isAggregate() || m_bGroupedBy))
         {
            // For cursors and max count queries, forbid subcollections in the
            // order by clause and append the OID

            for (int i = 0; i < m_nOrderByCount; ++i)
            {
               Operator op = getOrderByOperator(i);

               if (op.getOrdinal() == AttributeOperator.ORDINAL)
               {
                  if (op.getSource() == this)
                  {
                     bPartial = false;
                     break;
                  }

                  for (Query query = op.getSource().getQuery(); query != this; query = query.getParent())
                  {
                     if (query.getAttribute().isCollection())
                     {
                        throw new InvalidQueryException("err.persistence.orderBySubcollection");
                     }
                  }
               }
            }

            if (bPartial)
            {
               addOrderBy(new AttributeOperator(this), true);
            }
         }

         if (m_outputQueryList != null)
         {
            m_outputQueryList.clear();
         }

         m_rootList = new ArrayList(4);
         visit(NORMALIZATION_VISITOR, VISIT_ALL);
         visit(PLANNING_VISITOR, VISIT_QUERY);
         visit(FIELD_VISITOR, VISIT_ALL);

         if (isAggregate())
         {
            visit(IDENTITY_VISITOR, VISIT_QUERY);
         }

         visit(MAPPING_VISITOR, VISIT_ALL);
         normalizeGroupBy();
         normalizeOrderBy();
      }
   }

   /**
    * @return List of cached attribute symbols for a read specification.
    */
   protected Pair getCachedAttributes()
   {
      Pair attributes = null;

      for (int i = m_metaclass.getInstanceAttributeCount() - 1; i >= 0; --i)
      {
         Attribute attribute = m_metaclass.getInstanceAttribute(i);

         if (!attribute.isLazy())
         {
            attributes = new Pair(attribute.getSymbol(), attributes);
         }
      }

      return attributes;
   }

   /**
    * Reads the class instances with disabled caching.
    * @param attributes The read attribute list.
    * @param where The where clause.
    * @return The instance list.
    * @see #createRead(Metaclass, Pair, Object, Pair, int, int, boolean, byte, InvocationContext)
    */
   protected InstanceList readUncached(Pair attributes, Object where)
   {
      Query query = Query.createRead(m_metaclass, attributes, where, null, -1, 0, false, SEC_NONE, getInvocationContext());

      query.setLimit(-1);
      query.setCached(false);

      if (m_parent != null)
      {
         for (Iterator itr = query.getAssocIterator(ASSOC_QUERY); itr.hasNext();)
         {
            Query assoc = (Query)itr.next();

            if (assoc.getMetaclass() == m_parent.getMetaclass())
            {
               assoc.setCached(false);
            }
         }
      }

      return query.read();
   }

   /**
    * Caches a query result.
    * @param cacheMap The map of items to cache.
    * @param result The query result for determining the policy.
    */
   protected void cache(Lookup cacheMap, Object result)
   {
      boolean bDirty = false;

      if (result instanceof TransferObject)
      {
         bDirty = (((TransferObject)result).getEventName() != null);
      }
      else if (result instanceof List)
      {
         List list = (List)result;

         for (int i = 0, n = list.size(); i < n; ++i)
         {
            TransferObject tobj = (TransferObject)list.get(i);

            bDirty = (tobj.getEventName() != null);

            if (bDirty)
            {
               break;
            }
         }
      }

      InvocationContext context = getInvocationContext();

      if (!bDirty)
      {
         context.getGlobalCache().update(null, cacheMap, context.getUnitOfWork().getTime());
      }

      for (Lookup.Iterator itr = cacheMap.iterator(); itr.hasNext();)
      {
         itr.next();
         context.getUnitOfWork().cacheTemporary(itr.getKey(), itr.getValue(), UnitOfWork.CACHE_UNPARTITIONED);
      }
   }

   /**
    * Gets a cached query result.
    * @param The query result key.
    * @return The query result, or null if not found.
    */
   protected Pair getCached(Object key)
   {
      return (Pair)getInvocationContext().getUnitOfWork().getCached(key, UnitOfWork.CACHE_UNPARTITIONED);
   }

   /**
    * Reads the class instances specified by the query from cache.
    * @param The class instance list.
    */
   protected InstanceList readCached()
   {
      assert isCached();

      InvocationContext context = getInvocationContext();
      Lookup identityMap = new HashTab(4);
      boolean bCached = true;

      for (;;)
      {
         boolean bInstanceCaching = true;
         boolean bFiltered = false;
         boolean bMissed = false;
         List tobjList = null;

         if (m_persistenceMapping.getCaching() == PersistenceMapping.CACHING_CLASS)
         {
            Object classKey = Instance.getClassKey(m_metaclass, null);
            Pair classResult = getCached(classKey);
            Object queryKey = classKey;
            Pair queryResult = classResult;

            if (m_metaclass.getWhere() != null)
            {
               Query query = Query.createRead(m_metaclass, null, null, null, -1, 0, false, SEC_NONE, context);

               query.setLimit(-1);
               query.setCached(false);
               query.setCursor(false);
               query.prepare(false);

               if (query.getWhere() != null)
               {
                  queryKey = Instance.getClassKey(m_metaclass, query.toString(query.getWhere()));
                  queryResult = getCached(queryKey);
               }
            }

            if (bCached && queryResult != null && classResult != null &&
               ObjUtil.equal(queryResult.getTail(), classResult.getTail()))
            {
               tobjList = (List)queryResult.getHead();
            }

            if (tobjList == null)
            {
               Pair attributes = getCachedAttributes();
               InstanceList instanceList = readUncached(attributes, null);
               int nCount = instanceList.size();

               tobjList = new ArrayList(nCount);

               for (int i = 0; i < nCount; ++i)
               {
                  tobjList.add(RPCUtil.transfer(instanceList.get(i), attributes, identityMap,
                     RPCUtil.TF_ALL | RPCUtil.TF_LAZY | RPCUtil.TF_OLD));
                  identityMap.clear();
               }

               instanceList = null;

               Lookup cacheMap = new HashTab(2);

               queryResult = new Pair(tobjList);

               if (classKey != queryKey)
               {
                  classResult = getCached(classKey);

                  if (classResult == null)
                  {
                     classResult = new Pair(null, new Object());
                     cacheMap.put(classKey, classResult);
                  }
               }

               if (classResult != null)
               {
                  queryResult.setTail(classResult.getTail());
               }
               else
               {
                  queryResult.setTail(new Object());
               }

               cacheMap.put(queryKey, queryResult);
               cache(cacheMap, tobjList);
               bMissed = true;
            }

            bInstanceCaching = tobjList.size() > 64 && isUnique();
         }

         if (bInstanceCaching)
         {
            TransferObject tobj = null;
            Object instanceKey = null;
            Pair instanceResult = null;
            Object oidKey = null;
            Pair oidResult = null;
            OID oid;

            if (m_oid != null)
            {
               oid = m_oid;
            }
            else
            {
               oidKey = Pair.list(Symbol._CLASS, m_metaclass.getSymbol(), toString(m_where));
               oidResult = getCached(oidKey);
               oid = (oidResult == null) ? null : (OID)oidResult.getHead();
            }

            if (oid != null)
            {
               instanceKey = Instance.getInstanceKey(m_metaclass, oid);
               instanceResult = getCached(instanceKey);

               if (bCached && instanceResult != null &&
                  (m_oid != null || ObjUtil.equal(instanceResult.getTail(), oidResult.getTail())))
               {
                  tobj = (TransferObject)instanceResult.getHead();
               }
            }

            if (tobj == null)
            {
               tobj = NULL_TO;

               if (tobjList == null)
               {
                  Pair attributes = getCachedAttributes();
                  InstanceList instanceList;

                  if (m_oid != null)
                  {
                     instanceList = readUncached(attributes, Pair.attribute("").eq(m_oid));
                  }
                  else
                  {
                     int nLimitSaved = m_nLimit;
                     int nOffsetSaved = m_nOffset;
                     int nMaxCountSaved = m_nMaxCount;
                     byte nCachedSaved = m_nCached;

                     m_nLimit = -1;
                     m_nOffset = 0;
                     setCached(false);

                     try
                     {
                        instanceList = m_adapter.read(this);
                     }
                     finally
                     {
                        m_nLimit = nLimitSaved;
                        m_nOffset = nOffsetSaved;
                        m_nMaxCount = nMaxCountSaved;
                        m_nCached = nCachedSaved;
                     }

                     bFiltered = true;

                     for (Iterator itr = getAssocIterator(ASSOC_QUERY); itr.hasNext();)
                     {
                        Query assoc = (Query)itr.next();

                        if (assoc.isJoin() && !assoc.isInverse() && !assoc.getAttribute().isLazy())
                        {
                           assoc.join();
                        }
                     }
                  }

                  if (!instanceList.isEmpty())
                  {
                     tobj = (TransferObject)RPCUtil.transfer(instanceList.get(0), attributes,
                        identityMap, RPCUtil.TF_ALL | RPCUtil.TF_LAZY | RPCUtil.TF_OLD);
                     identityMap.clear();
                     oid = tobj.getOID();
                  }

                  bMissed = true;
               }
               else
               {
                  if (m_oid != null)
                  {
                     for (int i = 0, n = tobjList.size(); i < n; ++i)
                     {
                        TransferObject cur = (TransferObject)tobjList.get(i);

                        if (m_oid.equals(cur.getOID()))
                        {
                           tobj = cur;

                           break;
                        }
                     }
                  }
                  else
                  {
                     for (int i = 0, n = tobjList.size(); i < n; ++i)
                     {
                        TransferObject cur = (TransferObject)tobjList.get(i);

                        m_instance = cur;

                        if (m_where == null || !Boolean.FALSE.equals(m_where.getValue()))
                        {
                           tobj = cur;

                           break;
                        }
                     }

                     bFiltered = true;
                  }
               }

               if (oid != null)
               {
                  if (instanceKey == null)
                  {
                     instanceKey = Instance.getInstanceKey(m_metaclass, oid);
                  }

                  Lookup cacheMap = new HashTab(2);

                  instanceResult = (m_oid != null) ? null : getCached(instanceKey);

                  if (instanceResult == null)
                  {
                     instanceResult = new Pair(tobj);

                     if (tobj != NULL_TO)
                     {
                        if (m_persistenceMapping.getLockingAttribute() != null)
                        {
                           instanceResult.setTail(tobj.findValue(m_persistenceMapping.getLockingAttribute().getName()));
                        }

                        if (instanceResult.getTail() == null)
                        {
                           instanceResult.setTail(new Object());
                        }
                     }

                     cacheMap.put(instanceKey, instanceResult);
                  }

                  if (m_oid == null)
                  {
                     cacheMap.put(oidKey, new Pair(oid, instanceResult.getTail()));
                  }

                  cache(cacheMap, tobj);
               }
            }
            else
            {
               bMissed = false;
            }

            if (tobj != NULL_TO)
            {
               tobjList = Collections.singletonList(tobj);
            }
            else
            {
               tobjList = Collections.EMPTY_LIST;
            }
         }

         int nOffset = 0;
         int nCount = tobjList.size();
         InstanceList instanceList = new InstanceArrayList(nCount);
         InstanceFactory factory = new InstanceFactory(identityMap, null, InstanceFactory.STATE, context);

         for (int i = 0; i < nCount && (m_nMaxCount < 0 || instanceList.size() < m_nMaxCount); ++i)
         {
            TransferObject tobj = (TransferObject)tobjList.get(i);

            m_instance = tobj;

            if (bFiltered || m_where == null || !Boolean.FALSE.equals(m_where.getValue()))
            {
               Instance instance = factory.instantiate(tobj);

               identityMap.clear();

               if (isJoin())
               {
                  join(instance, getAdapter().getOID(instance, m_childItem));
               }

               if (nOffset++ >= m_nOffset)
               {
                  instanceList.add(instance);
               }
            }
         }

         if (bCached && !bMissed && factory.isLockMismatch())
         {
            bCached = false;

            continue;
         }

         if (getOrderByCount() != 0 && instanceList.size() > 1)
         {
            instanceList.sort(this);
         }

         m_instance = null;

         return instanceList;
      }
   }

   /**
    * Reads the class instances specified by the query.
    * @return The class instance list.
    */
   public InstanceList read()
   {
      assert isRoot();

      if (s_logger.isDumpEnabled())
      {
         s_logger.dump("Read: " + SysUtil.LINE_SEP + this);
      }

      int nLimit = getLimit();

      if (m_nMaxCount > nLimit && nLimit > 0)
      {
         throw new DataVolumeException("err.persistence.maxCount",
            new Object[]{Primitive.createInteger(m_nMaxCount), m_metaclass.getName(),
               Primitive.createInteger(nLimit)});
      }

      m_bCursor = false;
      prepare(m_nMaxCount >= 0);

      return m_adapter.read(this);
   }

   /**
    * Opens a cursor on the instances specified by the query.
    * @return The cursor.
    */
   public Cursor openCursor()
   {
      return openCursor(true);
   }

   /**
    * Opens a cursor on the instances specified by the query.
    * @param bComplete True if incompletely retrieved instances are prohibited.
    * @return The cursor.
    */
   public Cursor openCursor(boolean bComplete)
   {
      assert isRoot();

      if (s_logger.isDumpEnabled())
      {
         s_logger.dump("OpenCursor: " + SysUtil.LINE_SEP + this);
      }

      m_bCursor = true;
      prepare(bComplete);

      return m_adapter.openCursor(this);
   }

   /**
    * @see nexj.core.persistence.Source#getValue()
    */
   public Object getValue()
   {
      return m_instance;
   }

   /**
    * Gets the source value from the evaluated instance.
    * @param source The source.
    * @return The source value, or Undefined.VALUE if not available.
    */
   protected Object getValue(Source source)
   {
      if (!isRoot())
      {
         Object value = m_parent.getValue(this);

         if (value == null)
         {
            return value;
         }

         if (value instanceof PropertyMap)
         {
            return getAdapter().getValue((PropertyMap)value, source);
         }

         if (value instanceof OIDHolder)
         {
            return getAdapter().getValue(((OIDHolder)value).getOID(), source);
         }

         return Undefined.VALUE;
      }

      if (source == this)
      {
         return getValue();
      }

      if (m_instance == null)
      {
         return null;
      }

      return m_adapter.getValue(m_instance, source);
   }

   /**
    * Associates a parent instance to a source key OID for a heterogeneous query.
    * @param instance The parent instance.
    * @param oid The source key OID.
    */
   public void addParentInstance(Instance instance, OID oid)
   {
      assert isRoot() && m_parent != null;
      assert instance != null;
      assert oid != null;

      Set instanceSet;

      if (m_parentInstanceMap == null)
      {
         m_parentInstanceMap = new HashTab();
         instanceSet = null;
      }
      else
      {
         instanceSet = (Set)m_parentInstanceMap.get(oid);
      }

      if (instanceSet == null)
      {
         instanceSet = new HashHolder(2);
         m_parentInstanceMap.put(oid, instanceSet);
      }

      instanceSet.add(instance);
   }

   /**
    * Gets parent instances corresponding with the given source key OID.
    * @param oid The source key OID.
    * @return The parent instance set.
    */
   public Set getParentInstances(OID oid)
   {
      return (m_parentInstanceMap == null) ? null : (Set)m_parentInstanceMap.get(oid);
   }

   /**
    * Clears the parent instance map.
    */
   public void clearParentInstances()
   {
      if (m_parentInstanceMap != null)
      {
         m_parentInstanceMap.clear();
      }
   }

   /**
    * Joins a child instance with a specified destination key OID
    * to a matching parent instance in a heterogeneous query.
    * @param instance The child instance.
    * @param oid The destination key OID.
    */
   public void join(Instance instance, OID oid)
   {
      assert isJoin();
      assert instance != null;

      if (oid != null && m_parentInstanceMap != null)
      {
         Set instanceSet = (Set)m_parentInstanceMap.get(oid);

         if (instanceSet != null)
         {
            Attribute reverse = m_attribute.getReverse();

            if (reverse != null && reverse.isCollection())
            {
               reverse = null;
            }

            for (Iterator itr = instanceSet.iterator(); itr.hasNext();)
            {
               Instance container = (Instance)itr.next();

               if (m_attribute.isCollection())
               {
                  Object value = container.getOldValueDirect(m_attribute.getOrdinal());
                  InstanceList list;

                  if (value instanceof Undefined)
                  {
                     if (value == Undefined.VALUE)
                     {
                        list = new InstanceArrayList();
                        list.setAssociation(container, m_attribute, true);
                        container.setOldValueDirect(m_attribute.getOrdinal(), list);
                     }
                     else
                     {
                        list = null;
                     }
                  }
                  else
                  {
                     list = (InstanceList)value;
                  }

                  if (list != null)
                  {
                     list.setLazy(false);
                     list.add(instance, InstanceList.DIRECT | InstanceList.REPLACE);
                  }
               }
               else
               {
                  container.setOldValueDirect(m_attribute.getOrdinal(), instance);
               }

               if (reverse != null && !instance.isLazy() && instance.getOldValueDirect(reverse.getOrdinal()) == Undefined.VALUE)
               {
                  instance.setOldValueDirect(reverse.getOrdinal(), container);
               }
            }
         }
      }
   }

   /**
    * Joins the class instances specified in the query to the parent instances.
    */
   private void join()
   {
      assert isJoin();

      if (m_parentInstanceMap != null && m_parentInstanceMap.size() != 0 && !isLazy())
      {
         if (s_logger.isDebugEnabled())
         {
            s_logger.debug("Joining " + m_parent.getMetaclass().getName() + '.' +
               m_attribute.getName() + '(' + m_metaclass.getName() + ')' +
               ((isCached()) ? " from cache" : ""));
         }

         Set oidSet = new HashHolder(m_parentInstanceMap.size());

         for (Iterator itr = m_parentInstanceMap.iterator(); itr.hasNext();)
         {
            oidSet.add(itr.next());
         }

         AttributeOperator aop = new AttributeOperator(new Field(this, m_mapping, m_childItem, m_metaclass, null, null, false));

         if (isCached() && m_persistenceMapping.getCaching() == PersistenceMapping.CACHING_INSTANCE)
         {
            EqualsOperator eq = new EqualsOperator();

            eq.setLeft(aop);
            eq.setRight(new ConstantOperator(null));

            for (Iterator itr = oidSet.iterator(); itr.hasNext();)
            {
               EqualsOperator clone = (oidSet.size() == 1) ? eq : (EqualsOperator)eq.clone();

               clone.getRight().setValue(itr.next());
               read(clone);
            }
         }
         else
         {
            InOperator in = new InOperator();

            in.addOperand(aop);

            for (Iterator itr = oidSet.iterator(); itr.hasNext();)
            {
               in.addOperand(new ConstantOperator(itr.next()));
            }

            read(in);
         }

         completeParentInstances();
         clearParentInstances();
      }
   }

   /**
    * Completes the parent instances for which this child Query returned no results.
    */
   public void completeParentInstances()
   {
      assert isJoin();

      if (m_parentInstanceMap != null && m_parentInstanceMap.size() != 0)
      {
         for (Iterator setItr = m_parentInstanceMap.valueIterator(); setItr.hasNext();)
         {
            for (Iterator itr = ((Set)setItr.next()).iterator(); itr.hasNext();)
            {
               Instance container = (Instance)itr.next();

               if (m_attribute.isCollection())
               {
                  Object value = container.getOldValueDirect(m_attribute.getOrdinal());
                  InstanceList list;

                  if (value instanceof Undefined)
                  {
                     if (value == Undefined.VALUE)
                     {
                        list = new InstanceArrayList();
                        list.setAssociation(container, m_attribute, true);
                        container.setOldValueDirect(m_attribute.getOrdinal(), list);
                     }
                     else
                     {
                        list = null;
                     }
                  }
                  else
                  {
                     list = (InstanceList)value;
                  }

                  if (list != null)
                  {
                     list.setLazy(false);
                  }
               }
               else if (container.getOldValueDirect(m_attribute.getOrdinal()) == Undefined.VALUE)
               {
                  container.setOldValueDirect(m_attribute.getOrdinal(), null);
               }
            }
         }
      }
   }

   /**
    * Reads an instance list with an additional conjunction.
    * @param conj The additional conjunction.
    * @return The read instance list.
    */
   private InstanceList read(Operator conj)
   {
      byte nUniquenessSaved = m_nUnique;
      AndOperator and;
      int nOperandCountSaved;

      if (m_where == null)
      {
         and = null;
         nOperandCountSaved = 0;
         m_where = conj;
      }
      else
      {
         and = (AndOperator)m_where; // and was setup during normalization
         nOperandCountSaved = and.getOperandCount();
         and.addOperand(conj);
      }

      if (m_nUnique == 0)
      {
         m_nUnique = -1;
      }

      try
      {
         conj = conj.normalize(Operator.NORMALIZE_WHERE | Operator.NORMALIZE_PERSISTENCE);

         if (and == null)
         {
            m_where = conj;
         }
         else
         {
            and.setOperand(nOperandCountSaved, conj);
         }

         return read();
      }
      finally
      {
         // remove the join filter
         if (and == null)
         {
            m_where = null;
         }
         else
         {
            while (and.getOperandCount() > nOperandCountSaved)
            {
               and.removeOperand(and.getOperandCount() - 1);
            }
         }

         m_nUnique = nUniquenessSaved;
      }
   }

   /**
    * Completes the query execution plan.
    */
   public void complete()
   {
      if (m_parent == null)
      {
         for (int i = 1, n = m_rootList.size(); i < n; ++i)
         {
            ((Query)m_rootList.get(i)).join();
         }
      }
   }

   /**
    * Compares two property maps according to the order by clause.
    * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
    */
   public int compare(Object left, Object right)
   {
      int nResult = 0;

      for (int i = 0, n = getOrderByCount(); i < n; ++i)
      {
         Operator op = getOrderByOperator(i);

         m_instance = (PropertyMap)left;

         Object leftValue = op.getValue();

         m_instance = (PropertyMap)right;

         nResult = Primitive.COMPARATOR.compare(leftValue, op.getValue());

         if (nResult != 0)
         {
            if (!isOrderByAscending(i))
            {
               nResult = -nResult;
            }

            break;
         }
      }

      return nResult;
   }

   /**
    * Visits the query tree starting with this node in the specified order.
    * The root node is always visited.
    * @param visitor The tree visitor.
    * @param nFlags Combination of the VISIT_* constant bits.
    */
   public boolean visit(Visitor visitor, int nFlags)
   {
      return visit(visitor, nFlags, nFlags);
   }

   /**
    * Visits the query tree in the specified order, starting with this node.
    * The root node is always visited.
    * @param visitor The tree visitor.
    * @param nFlags Combination of the VISIT_* constant bits.
    * @param nChildFlags Flags for child node iteration.
    */
   public boolean visit(Visitor visitor, int nFlags, int nChildFlags)
   {
      if (!visitor.visit(this))
      {
         return false;
      }

      if ((nFlags & VISIT_QUERY) != 0 && m_queryArray != null)
      {
         for (int i = 0; i < m_queryArray.length; ++i)
         {
            Query assoc = m_queryArray[i];

            if (visitor.isEligible(assoc) && !assoc.visit(visitor, nChildFlags, nChildFlags))
            {
               return false;
            }
         }
      }

      if ((nFlags & VISIT_WHERE) != 0 && m_whereArray != null)
      {
         for (int i = 0; i < m_whereArray.length; ++i)
         {
            Query assoc = m_whereArray[i];

            if (visitor.isEligible(assoc) && !assoc.visit(visitor, nChildFlags, nChildFlags))
            {
               return false;
            }
         }
      }

      if ((nFlags & VISIT_QUANTOR) != 0 && m_quantorMap != null)
      {
         for (Iterator itr = m_quantorMap.valueIterator(); itr.hasNext();)
         {
            Query[] queryArray = (Query[])itr.next();

            for (int i = 0; i < queryArray.length; ++i)
            {
               Query assoc = queryArray[i];

               if (visitor.isEligible(assoc) && !assoc.visit(visitor, nChildFlags, nChildFlags))
               {
                  return false;
               }
            }
         }
      }

      return visitor.postVisit(this);
   }

   /**
    * @see java.lang.Comparable#compareTo(java.lang.Object)
    */
   public int compareTo(Object obj)
   {
      if (!(obj instanceof Query))
      {
         return getClass().getName().compareTo(obj.getClass().getName());
      }

      Query query = (Query)obj;

      int n = m_metaclass.compareTo(query.m_metaclass);

      if (n != 0)
      {
         return n;
      }

      if (m_attribute != null && query.m_attribute != null)
      {
         n = m_attribute.compareTo(query.m_attribute);

         if (n != 0)
         {
            return n;
         }
      }

      return ((m_bInverse) ? 1 : 0) - ((query.m_bInverse) ? 1 : 0);
   }

   /**
    * @see nexj.core.util.Printable#printOn(nexj.core.util.PrintWriter)
    */
   public void printOn(PrintWriter writer) throws IOException
   {
      Query querySaved;

      if (writer.getWriter() instanceof QueryHolder)
      {
         QueryHolder holder = (QueryHolder)writer.getWriter();

         querySaved = holder.getQuery();
         holder.setQuery(this);
      }
      else
      {
         querySaved = null;
      }

      writer.write("Query(");

      if (m_bInverse)
      {
         writer.write("inverse, ");
      }

      if (m_attribute != null)
      {
         writer.write("attribute=");
         writer.write(m_attribute.getName());
         writer.write(", ");
      }

      if (m_filter != null)
      {
         writer.write("filter=");
         writer.print(m_filter);
         writer.write(", ");
      }

      if (m_metaclass != null)
      {
         writer.write("class=");
         writer.write(m_metaclass.getName());
      }

      if (m_fieldMap.size() != 0)
      {
         writer.write(", fields={");

         boolean bFirst = true;

         for (Lookup.Iterator itr = m_fieldMap.iterator(); itr.hasNext(); bFirst = false)
         {
            if (!bFirst)
            {
               writer.write(", ");
            }

            Object key = itr.next();

            if (key instanceof Named)
            {
               writer.write(((Named)key).getName());
            }
            else
            {
               writer.print(key);
            }

            writer.write('=');
            writer.print(itr.getValue());
         }

         writer.write('}');
      }

      if (m_where != null)
      {
         writer.write(", where=");
         writer.print(m_where);
      }

      if (m_nGroupByCount != 0)
      {
         writer.write(", groupBy=(");

         for (int i = 0; i < m_nGroupByCount; ++i)
         {
            if (i != 0)
            {
               writer.write(' ');
            }

            writer.print(m_groupByArray[i]);
         }

         writer.write(')');
      }

      if (m_having != null)
      {
         writer.write(", having=");
         writer.print(m_having);
      }

      if (m_nOrderByCount != 0)
      {
         writer.write(", orderBy=(");

         for (int i = 0; i < m_nOrderByCount; ++i)
         {
            if (i != 0)
            {
               writer.write(' ');
            }

            writer.write('(');
            writer.print(m_orderByArray[i << 1]);
            writer.print(m_orderByArray[(i << 1) + 1] != null);
            writer.write(')');
         }

         writer.write(')');
      }

      if (m_nMaxCount != -1)
      {
         writer.write(", maxCount=");
         writer.print(m_nMaxCount);
      }

      if (m_nOffset != 0)
      {
         writer.write(", offset=");
         writer.print(m_nOffset);
      }

      if (m_bLocking)
      {
         writer.write(", xlock=");
         writer.write(String.valueOf(m_bLocking));
      }

      if (m_nOutput != OUTPUT_NONE)
      {
         writer.write(", output=");
         writer.write((m_nOutput == OUTPUT_LAZY) ? "lazy" : "eager");
      }

      if (m_mapping != null)
      {
         writer.write(", mapping=");
         writer.print(m_mapping);
      }

      if (m_generator != null)
      {
         writer.write(", generator=");
         writer.print(m_generator);
      }

      writer.write(')');

      if (m_queryArray != null)
      {
         writer.indent();
         writer.write("query[]:");

         for (int i = 0; i < m_queryArray.length; ++i)
         {
            writer.addIndent(1);
            writer.indent();
            writer.print(m_queryArray[i]);
            writer.addIndent(-1);
         }
      }

      if (m_whereArray != null)
      {
         writer.indent();
         writer.write("where[]:");

         for (int i = 0; i < m_whereArray.length; ++i)
         {
            writer.addIndent(1);
            writer.indent();
            writer.print(m_whereArray[i]);
            writer.addIndent(-1);
         }
      }

      if (writer.getWriter() instanceof QueryHolder)
      {
         ((QueryHolder)writer.getWriter()).setQuery(querySaved);
      }
   }

   /**
    * Converts an object to a string representation in the context of this query.
    * @param obj The object to convert. Can be null.
    */
   protected String toString(Printable obj)
   {
      QueryStringWriter writer = new QueryStringWriter(128);

      try
      {
         writer.setQuery(this);
         new PrintWriter(writer).print(obj);

         return writer.toString();
      }
      catch (IOException e)
      {
         throw ObjUtil.rethrow(e);
      }
   }

   /**
    * @see java.lang.Object#toString()
    */
   public String toString()
   {
      QueryStringWriter writer = new QueryStringWriter(128);

      try
      {
         new PrintWriter(writer).print(this);

         return writer.toString();
      }
      catch (IOException e)
      {
         return "#<I/O Error>";
      }
   }

   /**
    * Creates a read query object.
    * @param metaclass The primary class in the query.
    * @param attributes The attribute symbol list: (a1 a2 (a3 a3.1 ... a3.N) ... aN).
    * @param where The where clause as Scheme expression. Can be null to read everything.
    * @param orderBy The list of order by/asc flag expressions: ((e1 . #t) (e2 . #t) ... (eN . #t)).
    * @param nMaxCount The maximum number of primary class instances to retrieve.
    * @param nOffset The number of instances to skip.
    * @param bLock True to lock exclusively the retrieved primary class instances during the transaction.
    * @param nSecurity The security clause mode, one of the SEC_* constants.
    * @param context The invocation context.
    * @return The prepared query object.
    */
   public static Query createRead(Metaclass metaclass, Pair attributes, Object where, Pair orderBy,
      int nMaxCount, int nOffset, boolean bLock, byte nSecurity, InvocationContext context)
   {
      assert metaclass != null;

      Query root = new Query(metaclass, context);

      root.setSecurity(nSecurity);
      root.setWhere(where);
      root.setOutput(OUTPUT_EAGER);
      root.addDependency(ASSOC_QUERY, metaclass, metaclass, attributes, OUTPUT_EAGER);
      root.addOrderBy(orderBy);
      root.setMaxCount(nMaxCount);
      root.setOffset(nOffset);
      root.setLocking(bLock);
      root.setTimeout(context.getQueryTimeout());

      return root;
   }

   /**
    * Creates an aggregate query object.
    * @param metaclass The primary class in the query.
    * @param attributes The attribute symbol list: (a1 a2 (a3 a3.1 ... a3.N) ... aN).
    * @param where The where clause as Scheme expression. Can be null to aggregate everything.
    * @param groupBy The list of group by expressions: (e1 e2 ... eN).
    * @param having The having clause as Scheme expression. Can be null to read everything.
    * @param orderBy The list of order by/asc flag expressions: ((e1 . #t) (e2 . #t) ... (eN . #t)).
    * @param nMaxCount The maximum number of primary class instances to retrieve.
    * @param nOffset The number of instances to skip.
    * @param nSecurity The security clause mode, one of the SEC_* constants.
    * @param context The invocation context.
    * @return The prepared query object.
    */
   public static Query createAggregate(Metaclass metaclass, Pair attributes, Object where, Pair groupBy,
      Object having, Pair orderBy, int nMaxCount, int nOffset, byte nSecurity, InvocationContext context)
   {
      assert metaclass != null;

      Query root = new Query(metaclass, context);

      root.setAggregate(true);
      root.setSecurity(nSecurity);
      root.setWhere(where);
      root.addGroupBy(groupBy);
      root.setOutput(OUTPUT_EAGER);
      root.addDependency(ASSOC_QUERY, metaclass, metaclass, attributes, OUTPUT_EAGER);
      root.setHaving(having);
      root.addOrderBy(orderBy);
      root.setMaxCount(nMaxCount);
      root.setOffset(nOffset);
      root.setTimeout(context.getQueryTimeout());

      return root;
   }

   // inner classes

   /**
    * Interface for creating an operator from an S-expression.
    */
   protected interface OperatorFactory
   {
      /**
       * Creates an operator from an S-expression.
       * @param args The argument chain.
       * @param query The query to which to bind the operator.
       * @param key The association map key.
       * @param nOutput The attribute output mode, one of the OUTPUT_* constants.
       * @return The created operator, or null if not supported.
       */
      Operator create(Pair args, Query query, Object key, byte nOutput);
   }

   /**
    * Base class for creating a unary operator.
    */
   protected abstract static class UnaryOperatorFactory implements OperatorFactory
   {
      /**
       * @see nexj.core.persistence.Query.OperatorFactory#create(nexj.core.scripting.Pair, nexj.core.persistence.Query, Object, byte)
       */
      public Operator create(Pair args, Query query, Object key, byte nOutput)
      {
         UnaryOperator op = create(query);

         if (args == null || args.getTail() != null)
         {
            throw new InvalidQueryException("err.persistence.unaryOperatorArgCount",
               new Object[]{op.getSymbol()});
         }

         if (!op.setOperand(query.createOperator(key, args.getHead(), nOutput)))
         {
            return null;
         }

         return op;
      }

      /**
       * Creates the unary operator.
       * @param query The query which to bind to the operator.
       * @return The created unary operator.
       */
      public abstract UnaryOperator create(Query query);
   }

   /**
    * Base class for creating a binary operator.
    */
   protected abstract static class BinaryOperatorFactory implements OperatorFactory
   {
      /**
       * @see nexj.core.persistence.Query.OperatorFactory#create(nexj.core.scripting.Pair, nexj.core.persistence.Query, Object, byte)
       */
      public Operator create(Pair args, Query query, Object key, byte nOutput)
      {
         if (args == null)
         {
            throw new InvalidQueryException("err.persistence.binaryOperatorArgCount",
               new Object[]{create(query).getSymbol()});
         }

         if (args.getTail() == null)
         {
            return create(args.getHead(), query, key, nOutput);
         }

         BinaryOperator op = create(query);

         if (!op.setLeft(query.createOperator(key, args.getHead(), nOutput)))
         {
            return null;
         }

         args = (Pair)args.getTail();

         for (;;)
         {
            if (!op.setRight(query.createOperator(key, args.getHead(), nOutput)))
            {
               return null;
            }

            if (args.getTail() == null)
            {
               break;
            }

            BinaryOperator right = create(query);

            right.setLeft(op);
            op = right;
            args = args.getNext();
         }

         return op;
      }

      /**
       * Creates a unary operator from the same symbol, if only one argument was passed.
       * @param arg The argument for the unary operator.
       * @param query The query which to bind to the operator.
       * @param key The association map key.
       * @param nOutput The attribute output mode, one of the OUTPUT_* constants.
       * @return The created unary operator.
       */
      public UnaryOperator create(Object arg, Query query, Object key, byte nOutput)
      {
         throw new InvalidQueryException("err.persistence.binaryOperatorArgCount",
            new Object[]{create(query).getSymbol()});
      }

      /**
       * Creates the binary operator.
       * @param query The query which to bind to the operator.
       * @return The created binary operator.
       */
      public abstract BinaryOperator create(Query query);
   }

   /**
    * Base class for creating a comparison operator.
    */
   protected abstract static class ComparisonOperatorFactory implements OperatorFactory
   {
      /**
       * @see nexj.core.persistence.Query.OperatorFactory#create(nexj.core.scripting.Pair, nexj.core.persistence.Query, Object, byte)
       */
      public Operator create(Pair args, Query query, Object key, byte nOutput)
      {
         ComparisonOperator op = create(query);

         if (args == null || args.getTail() == null)
         {
            throw new InvalidQueryException("err.persistence.comparisonOperatorArgCount",
               new Object[]{op.getSymbol()});
         }

         if (!op.setLeft(query.createOperator(key, args.getHead(), nOutput)))
         {
            return null;
         }

         args = args.getNext();

         MultiArgOperator logical = null;

         for (;;)
         {
            if (!op.setRight(query.createOperator(key, args.getHead(), nOutput)))
            {
               return null;
            }

            op.initialize();

            if (args.getTail() == null)
            {
               break;
            }

            if (logical == null)
            {
               logical = createLogical(query);
               logical.addOperand(op);
            }

            ComparisonOperator right = create(query);

            if (!right.setLeft(query.createOperator(key, args.getHead(), nOutput)))
            {
               return null;
            }

            logical.addOperand(right);
            op = right;
            args = args.getNext();
         }

         if (logical != null)
         {
            return logical;
         }

         return op;
      }

      /**
       * Creates the comparison operator.
       * @param query The query which to bind to the operator.
       * @return The created comparison operator.
       */
      public abstract ComparisonOperator create(Query query);

      /**
       * Creates a logical operator for combining the comparisons.
       * @param query The query which to bind to the operator.
       * @return The logical operator.
       */
      public MultiArgOperator createLogical(Query query)
      {
         return new AndOperator();
      }
   }

   /**
    * Base class for creating a multi-arg operator.
    */
   protected abstract static class MultiArgOperatorFactory implements OperatorFactory
   {
      /**
       * @see nexj.core.persistence.Query.OperatorFactory#create(nexj.core.scripting.Pair, nexj.core.persistence.Query, Object, byte)
       */
      public Operator create(Pair args, Query query, Object key, byte nOutput)
      {
         MultiArgOperator op = create(query);

         for (; args != null; args = args.getNext())
         {
            if (op.addOperand(query.createOperator(key, args.getHead(), nOutput)) < 0)
            {
               return null;
            }
         }

         return op;
      }

      /**
       * Creates the multi-arg operator.
       * @param query The query which to bind to the operator.
       * @return The created multi-arg operator.
       */
      public abstract MultiArgOperator create(Query query);
   }

   /**
    * Factory for creating intrinsic function operators.
    */
   protected static class IntrinsicFunctionOperatorFactory implements OperatorFactory
   {
      // associations

      /**
       * The intrinsic function for evaluating the operator.
       */
      protected IntrinsicFunction m_fun;

      /**
       * The result type.
       */
      protected Primitive m_type;

      /**
       * Array of argument types, indexed by argument ordinal number.
       */
      protected Primitive[] m_argTypeArray;

      // constructor

      /**
       * Constructs the factory.
       * @param fun The function implementation.
       * @param type The function return type.
       * @param argTypeArray The argument type array, indexed by the argument ordinal number.
       */
      public IntrinsicFunctionOperatorFactory(IntrinsicFunction fun, Primitive type, Primitive[] argTypeArray)
      {
         m_fun = fun;
         m_type = type;
         m_argTypeArray = argTypeArray;
      }

      // operations

      /**
       * @see nexj.core.persistence.Query.OperatorFactory#create(nexj.core.scripting.Pair, nexj.core.persistence.Query, java.lang.Object, byte)
       */
      public Operator create(Pair args, Query query, Object key, byte nOutput)
      {
         if (Pair.length(args) != m_argTypeArray.length)
         {
            return null;
         }

         FunctionOperator op = new IntrinsicFunctionOperator(m_fun, m_type, m_argTypeArray);

         for (; args != null; args = args.getNext())
         {
            if (op.addOperand(query.createOperator(key, args.getHead(), nOutput)) < 0)
            {
               return null;
            }
         }

         return op;
      }
   }

   /**
    * Factory for creating aggregate function operators.
    */
   protected abstract static class AggregateFunctionOperatorFactory implements OperatorFactory
   {
      // operations

      /**
       * @see nexj.core.persistence.Query.OperatorFactory#create(nexj.core.scripting.Pair, nexj.core.persistence.Query, java.lang.Object, byte)
       */
      public Operator create(Pair args, Query query, Object key, byte nOutput)
      {
         if (args == null || args.getTail() != null)
         {
            return null;
         }

         AggregateOperator op = create(query);

         if (!op.setOperand(query.createOperator(
            (op instanceof UniqueOperator || query.isGroupedBy() || query.isAggregate()) ? key : op, args.getHead(),
            (nOutput == OUTPUT_UNKNOWN) ? OUTPUT_UNKNOWN : OUTPUT_NONE)))
         {
            return null;
         }

         return op;
      }

      /**
       * Template factory method.
       * @param query The operator query node.
       * @return A new instance of the operator.
       */
      protected abstract AggregateOperator create(Query query);
   }

   /**
    * Query visitor interface.
    * @see Query#visit(Visitor, int, int)
    */
   public interface Visitor
   {
      /**
       * Visits a query node in pre-order (parent first).
       * @param query The query node to visit.
       * @return True if the iteration on the same level should continue.
       */
      boolean visit(Query query);

      /**
       * Visits a query node in post-order (parent last).
       * @param query The query node to visit.
       * @return True if the iteration on the same level should continue.
       */
      boolean postVisit(Query query);

      /**
       * Checks if a query node is eligible for visiting.
       * @param query The query node to check.
       * @return True if it is eligible for visiting.
       */
      boolean isEligible(Query query);
   }

   /**
    * Subtree visitor - visits the nodes with the same subtree root.
    */
   public abstract static class SubtreeVisitor implements Visitor
   {
      public boolean postVisit(Query query)
      {
         return true;
      }

      /**
       * @see nexj.core.persistence.Query.Visitor#isEligible(nexj.core.persistence.Query)
       */
      public boolean isEligible(Query query)
      {
         return query.isSameRoot(query.getParent());
      }
   }

   /**
    * StringWriter providing the query context to the Printable objects.
    */
   protected static class QueryStringWriter extends StringWriter implements QueryHolder
   {
      // associations

      /**
       * The query.
       */
      protected Query m_query;

      // constructors

      /**
       * Constructs the writer.
       * @param nCount Preallocated character count.
       */
      public QueryStringWriter(int nCount)
      {
         super(nCount);
      }

      // operations

      /**
       * @see nexj.core.persistence.QueryHolder#setQuery(nexj.core.persistence.Query)
       */
      public void setQuery(Query query)
      {
         m_query = query;
      }

      /**
       * @see nexj.core.persistence.QueryHolder#getQuery()
       */
      public Query getQuery()
      {
         return m_query;
      }
   }

   /**
    * Visitor for computing query dependencies.
    */
   protected static class QueryDepVisitor implements Operator.Visitor
   {
      protected int m_nCount;
      protected Query[] m_depArray;
      protected Query[] m_queryArray;

      public QueryDepVisitor(Query[] queryArray)
      {
         m_depArray = new Query[queryArray.length - 1];
         m_queryArray = queryArray;
      }

      public Query[] findDeps(Query query)
      {
         if (query.m_where != null)
         {
            m_nCount = 0;

            query.m_where.visit(this, Operator.VISIT_PREORDER);

            if (m_nCount != 0)
            {
               Query[] depArray = new Query[m_nCount];

               System.arraycopy(m_depArray, 0, depArray, 0, m_nCount);

               return depArray;
            }
         }

         return null;
      }

      public boolean visit(Operator op)
      {
         Source source = op.getSource();

         if (source != null)
         {
            source = source.getQuery().getSource();
         }

      loop:
         for (int i = 0; i < m_queryArray.length; ++i)
         {
            Query query = m_queryArray[i];

            if (query.getSource() == source)
            {
               for (int k = 0; k < m_nCount; ++k)
               {
                  if (m_depArray[k] == query)
                  {
                     break loop;
                  }
               }

               m_depArray[m_nCount++] = query;

               break;
            }
         }

         return true;
      }

      public boolean isEligible(Operator op)
      {
         return true;
      }
   }
}
TOP

Related Classes of nexj.core.persistence.Query$QueryDepVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.