context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Text;
  using NHibernate.Criterion;
  using NHibernate.Engine;
  using NHibernate.Multi;
  using NHibernate.SqlCommand;
  using NHibernate.Transform;
  using NHibernate.Util;
  
  namespace NHibernate.Impl
  {
  	/// <summary>
  	/// Implementation of the <see cref="ICriteria"/> interface
  	/// </summary>
  	[Serializable]
  	public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria, ISupportSelectModeCriteria
  	{
  		private readonly System.Type persistentClass;
  		private readonly List<CriterionEntry> criteria = new List<CriterionEntry>();
  		private readonly List<OrderEntry> orderEntries = new List<OrderEntry>(10);
  		private readonly Dictionary<string, SelectMode> selectModes = new Dictionary<string, SelectMode>();
  		private readonly Dictionary<string, LockMode> lockModes = new Dictionary<string, LockMode>();
  		private readonly Dictionary<string, HashSet<string>> _entityFetchLazyProperties = new Dictionary<string, HashSet<string>>();
  
  		private int maxResults = RowSelection.NoValue;
  		private int firstResult;
  		private int timeout = RowSelection.NoValue;
  		private int fetchSize = RowSelection.NoValue;
  		private ISessionImplementor session;
  		private IResultTransformer resultTransformer = CriteriaSpecification.RootEntity;
  		private bool cacheable;
  		private string cacheRegion;
  		private CacheMode? cacheMode;
  		private CacheMode? sessionCacheMode;
  		private string comment;
  		private FlushMode? flushMode;
  		private FlushMode? sessionFlushMode;
  		private bool? readOnly;
  
  		private readonly List<Subcriteria> subcriteriaList = new List<Subcriteria>();
  		private readonly string rootAlias;
  
  		private readonly Dictionary<string, ICriteria> subcriteriaByPath = new Dictionary<string, ICriteria>();
  		private readonly Dictionary<string, ICriteria> subcriteriaByAlias = new Dictionary<string, ICriteria>();
  		private readonly string entityOrClassName;
  
  		// Projection Fields
  		private IProjection projection;
  		private ICriteria projectionCriteria;
  
  		public CriteriaImpl(System.Type persistentClass, ISessionImplementor session)
  			: this(persistentClass.FullName, CriteriaSpecification.RootAlias, session)
  		{
  			this.persistentClass = persistentClass;
  		}
  
  		public CriteriaImpl(System.Type persistentClass, string alias, ISessionImplementor session)
  			: this(persistentClass.FullName, alias, session)
  		{
  			this.persistentClass = persistentClass;
  		}
  
  		public CriteriaImpl(string entityOrClassName, ISessionImplementor session)
  			: this(entityOrClassName, CriteriaSpecification.RootAlias, session) {}
  
  		public CriteriaImpl(string entityOrClassName, string alias, ISessionImplementor session)
  		{
  			this.session = session;
  			this.entityOrClassName = entityOrClassName;
  			cacheable = false;
  			rootAlias = alias;
  			subcriteriaByAlias[alias] = this;
  		}
  
  		public ISessionImplementor Session
  		{
  			get { return session; }
  			set { session = value; }
  		}
  
  		public string EntityOrClassName
  		{
  			get { return entityOrClassName; }
  		}
  
  		public IDictionary<string, LockMode> LockModes
  		{
  			get { return lockModes; }
  		}
  
  		public ICriteria ProjectionCriteria
  		{
  			get { return projectionCriteria; }
  		}
  
  		public bool LookupByNaturalKey
  		{
  			get
  			{
  				if (projection != null)
  				{
  					return false;
  				}
  				if (subcriteriaList.Count > 0)
  				{
  					return false;
  				}
  				if (criteria.Count != 1)
  				{
  					return false;
  				}
  
  				CriterionEntry ce = criteria[0];
  				return ce.Criterion is NaturalIdentifier;
  			}
  		}
  
  		public string Alias
  		{
  			get { return rootAlias; }
  		}
  
  		public IProjection Projection
  		{
  			get { return projection; }
  		}
  		
  		/// <inheritdoc />
  		public bool IsReadOnlyInitialized
  		{
  			get { return (readOnly != null); }
  		}
  		
  		/// <inheritdoc />
  		public bool IsReadOnly
  		{
  			get
  			{
  				if (!IsReadOnlyInitialized && (Session == null))
  					throw new InvalidOperationException("cannot determine readOnly/modifiable setting when it is not initialized and is not initialized and Session == null");
  
  				return IsReadOnlyInitialized ? readOnly.Value : Session.PersistenceContext.DefaultReadOnly;
  			}
  		}
  
  		//Since 5.2
  		[Obsolete("Use GetSelectMode instead")]
  		public FetchMode GetFetchMode(string path)
  		{
  			switch (GetSelectMode(path))
  			{
  				case SelectMode.Undefined:
  					return FetchMode.Default;
  				case SelectMode.Skip:
  					return FetchMode.Lazy;
  				default:
  					return FetchMode.Join;
  			}
  		}
  		
  		public SelectMode GetSelectMode(string path)
  		{
  			if (!selectModes.TryGetValue(path, out var result))
  			{
  				result = SelectMode.Undefined;
  			}
  			return result;
  		}
  
  		public HashSet<string> GetEntityFetchLazyProperties(string path)
  		{
  			if (_entityFetchLazyProperties.TryGetValue(path, out var result))
  			{
  				return result;
  			}
  			return null;
  		}
  
  		public IResultTransformer ResultTransformer
  		{
  			get { return resultTransformer; }
  		}
  
  		public int MaxResults
  		{
  			get { return maxResults; }
  		}
  
  		public int FirstResult
  		{
  			get { return firstResult; }
  		}
  
  		public int FetchSize
  		{
  			get { return fetchSize; }
  		}
  
  		public int Timeout
  		{
  			get { return timeout; }
  		}
  
  		public bool Cacheable
  		{
  			get { return cacheable; }
  		}
  
  		public string CacheRegion
  		{
  			get { return cacheRegion; }
  		}
  
  		public CacheMode? CacheMode => cacheMode;
  
  		public string Comment
  		{
  			get { return comment; }
  		}
  
  		protected internal void Before()
  		{
  			if (flushMode.HasValue)
  			{
  				sessionFlushMode = Session.FlushMode;
  				Session.FlushMode = flushMode.Value;
  			}
  			if (cacheMode.HasValue)
  			{
  				sessionCacheMode = Session.CacheMode;
  				Session.CacheMode = cacheMode.Value;
  			}
  		}
  
  		protected internal void After()
  		{
  			if (sessionFlushMode.HasValue)
  			{
  				Session.FlushMode = sessionFlushMode.Value;
  				sessionFlushMode = null;
  			}
  			if (sessionCacheMode.HasValue)
  			{
  				Session.CacheMode = sessionCacheMode.Value;
  				sessionCacheMode = null;
  			}
  		}
  
  		public ICriteria SetMaxResults(int maxResults)
  		{
  			this.maxResults = maxResults;
  			return this;
  		}
  
  		public ICriteria SetFirstResult(int firstResult)
  		{
  			this.firstResult = firstResult;
  			return this;
  		}
  
  		public ICriteria SetTimeout(int timeout)
  		{
  			this.timeout = timeout;
  			return this;
  		}
  
  		public ICriteria SetFetchSize(int fetchSize)
  		{
  			this.fetchSize = fetchSize;
  			return this;
  		}
  
  		public ICriteria Add(ICriterion expression)
  		{
  			Add(this, expression);
  			return this;
  		}
  
  		public IList List()
  		{
  			return List<object>().ToIList();
  		}
  
  		public void List(IList results)
  		{
  			ArrayHelper.AddAll(results, List());
  		}
  
  		public IList<T> List<T>()
  		{
  			Before();
  			try
  			{
  				return session.List<T>(this);
  			}
  			finally
  			{
  				After();
  			}
  		}
  
  		public T UniqueResult<T>()
  		{
  			object result = UniqueResult();
  			if (result == null && typeof (T).IsValueType)
  			{
  				return default(T);
  			}
  			else
  			{
  				return (T) result;
  			}
  		}
  
  		public void ClearOrders()
  		{
  			orderEntries.Clear();
  		}
  
  		public IEnumerable<CriterionEntry> IterateExpressionEntries()
  		{
  			return criteria;
  		}
  
  		public IEnumerable<OrderEntry> IterateOrderings()
  		{
  			return orderEntries;
  		}
  
  		public IEnumerable<Subcriteria> IterateSubcriteria()
  		{
  			return subcriteriaList;
  		}
  
  		public override string ToString()
  		{
  			bool first = true;
  			StringBuilder builder = new StringBuilder();
  			foreach (CriterionEntry criterionEntry in criteria)
  			{
  				if (!first)
  				{
  					builder.Append(" and ");
  				}
  				builder.Append(criterionEntry.ToString());
  				first = false;
  			}
  			if (orderEntries.Count != 0)
  			{
  				builder.AppendLine();
  			}
  			first = true;
  			foreach (OrderEntry orderEntry in orderEntries)
  			{
  				if (!first)
  				{
  					builder.Append(" and ");
  				}
  				builder.Append(orderEntry.ToString());
  				first = false;
  			}
  			return builder.ToString();
  		}
  
  		public ICriteria Fetch(SelectMode selectMode, string associationPath, string alias)
  		{
  			if (!string.IsNullOrEmpty(alias))
  			{
  				var criteriaByAlias = GetCriteriaByAlias(alias);
  				criteriaByAlias.Fetch(selectMode, associationPath, null);
  				return this;
  			}
  
  			if (selectMode == SelectMode.FetchLazyPropertyGroup)
  			{
  				StringHelper.ParsePathAndPropertyName(associationPath, out associationPath, out var propertyName);
  				if (_entityFetchLazyProperties.TryGetValue(associationPath, out var propertyNames))
  				{
  					propertyNames.Add(propertyName);
  				}
  				else
  				{
  					_entityFetchLazyProperties[associationPath] = new HashSet<string> {propertyName};
  				}
  			}
  
  			selectModes[associationPath] = selectMode;
  			return this;
  		}
  
  		public ICriteria AddOrder(Order ordering)
  		{
  			orderEntries.Add(new OrderEntry(ordering, this));
  			return this;
  		}
  
  		//Since 5.2
  		[Obsolete("Use Fetch instead")]
  		public ICriteria SetFetchMode(string associationPath, FetchMode mode)
  		{
  			Fetch(GetSelectMode(mode), associationPath, null);
  			return this;
  		}
  
  		//Since 5.2
  		[Obsolete]
  		private SelectMode GetSelectMode(FetchMode mode)
  		{
  			switch (mode)
  			{
  				case FetchMode.Default:
  					return SelectMode.Undefined;
  
  				case FetchMode.Select:
  					return SelectMode.Skip;
  
  				case FetchMode.Join:
  					return SelectMode.Fetch;
  
  				default:
  					throw new ArgumentOutOfRangeException(nameof(mode), mode, null);
  			}
  		}
  
  		public ICriteria CreateAlias(string associationPath, string alias)
  		{
  			CreateAlias(associationPath, alias, JoinType.InnerJoin);
  			return this;
  		}
  
  		public ICriteria CreateAlias(string associationPath, string alias, JoinType joinType)
  		{
  			new Subcriteria(this, this, associationPath, alias, joinType);
  			return this;
  		}
  
  		public ICriteria CreateAlias(string associationPath, string alias, JoinType joinType, ICriterion withClause)
  		{
  			new Subcriteria(this, this, associationPath, alias, joinType, withClause);
  			return this;
  		}
  
  		public ICriteria CreateEntityCriteria(string alias, ICriterion withClause, JoinType joinType, string entityName)
  		{
  			return new Subcriteria(this, this, alias, alias, joinType, withClause, entityName);
  		}
  
  		public ICriteria Add(ICriteria criteriaInst, ICriterion expression)
  		{
  			criteria.Add(new CriterionEntry(expression, criteriaInst));
  			return this;
  		}
  
  		public ICriteria CreateCriteria(string associationPath)
  		{
  			return CreateCriteria(associationPath, JoinType.InnerJoin);
  		}
  
  		public ICriteria CreateCriteria(string associationPath, JoinType joinType)
  		{
  			return new Subcriteria(this, this, associationPath, joinType);
  		}
  
  		public ICriteria CreateCriteria(string associationPath, string alias)
  		{
  			return CreateCriteria(associationPath, alias, JoinType.InnerJoin);
  		}
  
  		public ICriteria CreateCriteria(string associationPath, string alias, JoinType joinType)
  		{
  			return new Subcriteria(this, this, associationPath, alias, joinType);
  		}
  
  		public ICriteria CreateCriteria(string associationPath, string alias, JoinType joinType, ICriterion withClause)
  		{
  			return new Subcriteria(this, this, associationPath, alias, joinType, withClause);
  		}
  
  		public IFutureValue<T> FutureValue<T>()
  		{
  			return session.GetFutureBatch().AddAsFutureValue<T>(this);
  		}
  
  		public IFutureEnumerable<T> Future<T>()
  		{
  			return session.GetFutureBatch().AddAsFuture<T>(this);
  		}
  
  		public object UniqueResult()
  		{
  			return AbstractQueryImpl.UniqueElement(List());
  		}
  
  		public ICriteria SetLockMode(LockMode lockMode)
  		{
  			return SetLockMode(CriteriaSpecification.RootAlias, lockMode);
  		}
  
  		public ICriteria SetLockMode(string alias, LockMode lockMode)
  		{
  			lockModes[alias] = lockMode;
  			return this;
  		}
  
  		public ICriteria SetResultTransformer(IResultTransformer tupleMapper)
  		{
  			resultTransformer = tupleMapper;
  			return this;
  		}
  
  		public ICriteria SetCacheable(bool cacheable)
  		{
  			this.cacheable = cacheable;
  			return this;
  		}
  
  		public ICriteria SetCacheRegion(string cacheRegion)
  		{
  			this.cacheRegion = cacheRegion.Trim();
  			return this;
  		}
  
  		public ICriteria SetComment(string comment)
  		{
  			this.comment = comment;
  			return this;
  		}
  
  		public ICriteria SetFlushMode(FlushMode flushMode)
  		{
  			this.flushMode = flushMode;
  			return this;
  		}
  
  		public ICriteria SetProjection(params IProjection[] projections)
  		{
  			if(projections==null)
  				throw new ArgumentNullException("projections");
  			if(projections.Length ==0)
  				throw new ArgumentException("projections must contain a least one projection");
  
  			if(projections.Length==1)
  			{
  				projection = projections[0];
  			}
  			else
  			{
  				var projectionList = new ProjectionList();
  				foreach (var childProjection in projections)
  				{
  					projectionList.Add(childProjection);
  				}
  				projection = projectionList;
  			}
  
  			if (projection != null)
  			{
  				projectionCriteria = this;
  				SetResultTransformer(CriteriaSpecification.Projection);
  			}
  
  			return this;
  		}
  
  		/// <inheritdoc />
  		public ICriteria SetReadOnly(bool readOnly)
  		{
  			this.readOnly = readOnly;
  			return this;
  		}
  
  		/// <summary> Override the cache mode for this particular query. </summary>
  		/// <param name="cacheMode">The cache mode to use. </param>
  		/// <returns> this (for method chaining) </returns>
  		public ICriteria SetCacheMode(CacheMode cacheMode)
  		{
  			this.cacheMode = cacheMode;
  			return this;
  		}
  
  		public object Clone()
  		{
  			CriteriaImpl clone;
  			if (persistentClass != null)
  			{
  				clone = new CriteriaImpl(persistentClass, Alias, Session);
  			}
  			else
  			{
  				clone = new CriteriaImpl(entityOrClassName, Alias, Session);
  			}
  			CloneSubcriteria(clone);
  			foreach (KeyValuePair<string, LockMode> de in lockModes)
  			{
  				clone.lockModes.Add(de.Key, de.Value);
  			}
  			clone.selectModes.AddOrOverride(selectModes);
  			clone.maxResults = maxResults;
  			clone.firstResult = firstResult;
  			clone.timeout = timeout;
  			clone.fetchSize = fetchSize;
  			clone.cacheable = cacheable;
  			clone.cacheRegion = cacheRegion;
  			clone.SetProjection(projection);
  			CloneProjectCrtieria(clone);
  			clone.SetResultTransformer(resultTransformer);
  			clone.comment = comment;
  			clone.readOnly = readOnly;
  			if (flushMode.HasValue)
  			{
  				clone.SetFlushMode(flushMode.Value);
  			}
  			if (cacheMode.HasValue)
  			{
  				clone.SetCacheMode(cacheMode.Value);
  			}
  			return clone;
  		}
  
  		private void CloneProjectCrtieria(CriteriaImpl clone)
  		{
  			if (projectionCriteria != null)
  			{
  				if (projectionCriteria == this)
  				{
  					clone.projectionCriteria = clone;
  				}
  				else
  				{
  					ICriteria clonedProjectionCriteria = (ICriteria) projectionCriteria.Clone();
  					clone.projectionCriteria = clonedProjectionCriteria;
  				}
  			}
  		}
  
  		private void CloneSubcriteria(CriteriaImpl clone)
  		{
  			//we need to preserve the parent criteria, we rely on the ordering when creating the
  			//subcriterias initially here, so we don't need to make more than a single pass
  			Dictionary<ICriteria, ICriteria> newParents = new Dictionary<ICriteria, ICriteria>();
  			newParents[this] = clone;
  
  			foreach (Subcriteria subcriteria in IterateSubcriteria())
  			{
  				ICriteria currentParent;
  				if (!newParents.TryGetValue(subcriteria.Parent, out currentParent))
  				{
  					throw new AssertionFailure(
  						"Could not find parent for subcriteria in the previous subcriteria. If you see this error, it is a bug");
  				}
  				Subcriteria clonedSubCriteria =
  					new Subcriteria(clone, currentParent, subcriteria.Path, subcriteria.Alias, subcriteria.JoinType, subcriteria.WithClause, subcriteria.JoinEntityName);
  				clonedSubCriteria.SetLockMode(subcriteria.LockMode);
  				newParents[subcriteria] = clonedSubCriteria;
  			}
  
  			// remap the orders
  			foreach (OrderEntry orderEntry in IterateOrderings())
  			{
  				ICriteria currentParent;
  				if (!newParents.TryGetValue(orderEntry.Criteria, out currentParent))
  				{
  					throw new AssertionFailure(
  						"Could not find parent for order in the previous criteria. If you see this error, it is a bug");
  				}
  				currentParent.AddOrder(orderEntry.Order);
  			}
  
  			// remap the restrictions to appropriate criterias
  			foreach (CriterionEntry criterionEntry in criteria)
  			{
  				ICriteria currentParent;
  				if (!newParents.TryGetValue(criterionEntry.Criteria, out currentParent))
  				{
  					throw new AssertionFailure(
  						"Could not find parent for restriction in the previous criteria. If you see this error, it is a bug.");
  				}
  
  				currentParent.Add(criterionEntry.Criterion);
  			}
  		}
  
  		public ICriteria GetCriteriaByPath(string path)
  		{
  			ICriteria result;
  			subcriteriaByPath.TryGetValue(path, out result);
  			return result;
  		}
  
  		public ICriteria GetCriteriaByAlias(string alias)
  		{
  			ICriteria result;
  			subcriteriaByAlias.TryGetValue(alias, out result);
  			return result;
  		}
  
  		[Serializable]
  		public sealed partial class Subcriteria : ICriteria, ISupportSelectModeCriteria
  		{
  			// Added to simulate Java-style inner class
  			private readonly CriteriaImpl root;
  
  			private readonly ICriteria parent;
  			private string alias;
  			private readonly string path;
  			private LockMode lockMode;
  			private readonly JoinType joinType;
  			private ICriterion withClause;
  			private bool hasRestrictions;
  
  			internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string alias, JoinType joinType, ICriterion withClause, string joinEntityName = null)
  			{
  				this.root = root;
  				this.parent = parent;
  				this.alias = alias;
  				this.path = path;
  				this.joinType = joinType;
  				this.withClause = withClause;
  				JoinEntityName = joinEntityName;
  				hasRestrictions = withClause != null;
  
  				root.subcriteriaList.Add(this);
  
  				root.subcriteriaByPath[path] = this;
  				SetAlias(alias);
  			}
  
  			internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, string alias, JoinType joinType)
  				: this(root, parent, path, alias, joinType, null) {}
  
  			internal Subcriteria(CriteriaImpl root, ICriteria parent, string path, JoinType joinType)
  				: this(root, parent, path, null, joinType) { }
  
  			/// <summary>
  			/// Entity name for "Entity Join" - join for entity with not mapped association
  			/// </summary>
  			public string JoinEntityName { get; }
  
  			/// <summary>
  			/// Is this an Entity join for not mapped association
  			/// </summary>
  			public bool IsEntityJoin => JoinEntityName != null;
  
  			public ICriterion WithClause
  			{
  				get { return withClause; }
  			}
  
  			public string Path
  			{
  				get { return path; }
  			}
  
  			public bool HasRestrictions
  			{
  				get { return hasRestrictions; }
  			}
  
  			public ICriteria Parent
  			{
  				get { return parent; }
  			}
  
  			public JoinType JoinType
  			{
  				get { return joinType; }
  			}
  
  			public string Alias
  			{
  				get { return alias; }
  				set { SetAlias(value); }
  			}
  
  			public LockMode LockMode
  			{
  
gt: 				get { return lockMode; }