context: /* Copyright (C) 2004 - 2009  Versant Inc.  http://www.db4o.com */
  
  using System;
  using System.Collections;
  using Db4objects.Db4o;
  using Db4objects.Db4o.Config;
  using Db4objects.Db4o.Ext;
  using Db4objects.Db4o.Foundation;
  using Db4objects.Db4o.Internal;
  using Db4objects.Db4o.Internal.Btree;
  using Db4objects.Db4o.Internal.Convert;
  using Db4objects.Db4o.Internal.Events;
  using Db4objects.Db4o.Internal.Fileheader;
  using Db4objects.Db4o.Internal.Freespace;
  using Db4objects.Db4o.Internal.Ids;
  using Db4objects.Db4o.Internal.Qlin;
  using Db4objects.Db4o.Internal.Query.Processor;
  using Db4objects.Db4o.Internal.Query.Result;
  using Db4objects.Db4o.Internal.References;
  using Db4objects.Db4o.Internal.Slots;
  using Db4objects.Db4o.Qlin;
  using Sharpen;
  using Sharpen.Lang;
  
  namespace Db4objects.Db4o.Internal
  {
  	/// <exclude></exclude>
  	public abstract class LocalObjectContainer : ExternalObjectContainer, IInternalObjectContainer
  		, IEmbeddedObjectContainer
  	{
  		protected FileHeader _fileHeader;
  
  		private readonly Collection4 _dirtyClassMetadata = new Collection4();
  
  		private IFreespaceManager _freespaceManager;
  
  		private bool i_isServer = false;
  
  		private Lock4 _semaphoresLock = new Lock4();
  
  		private Hashtable4 _semaphores;
  
  		private int _blockEndAddress;
  
  		private Db4objects.Db4o.Internal.SystemData _systemData;
  
  		private IIdSystem _idSystem;
  
  		private readonly byte[] _pointerBuffer = new byte[Const4.PointerLength];
  
  		protected readonly ByteArrayBuffer _pointerIo = new ByteArrayBuffer(Const4.PointerLength
  			);
  
  		internal LocalObjectContainer(IConfiguration config) : base(config)
  		{
  		}
  
  		public override Transaction NewTransaction(Transaction parentTransaction, IReferenceSystem
  			 referenceSystem, bool isSystemTransaction)
  		{
  			ITransactionalIdSystem systemIdSystem = null;
  			if (!isSystemTransaction)
  			{
  				systemIdSystem = SystemTransaction().IdSystem();
  			}
  			IClosure4 idSystem = new _IClosure4_58(this);
  			ITransactionalIdSystem transactionalIdSystem = NewTransactionalIdSystem(systemIdSystem
  				, idSystem);
  			return new LocalTransaction(this, parentTransaction, transactionalIdSystem, referenceSystem
  				);
  		}
  
  		private sealed class _IClosure4_58 : IClosure4
  		{
  			public _IClosure4_58(LocalObjectContainer _enclosing)
  			{
  				this._enclosing = _enclosing;
  			}
  
  			public object Run()
  			{
  				return this._enclosing.IdSystem();
  			}
  
  			private readonly LocalObjectContainer _enclosing;
  		}
  
  		public virtual ITransactionalIdSystem NewTransactionalIdSystem(ITransactionalIdSystem
  			 systemIdSystem, IClosure4 idSystem)
  		{
  			return new TransactionalIdSystemImpl(new _IClosure4_69(this), idSystem, (TransactionalIdSystemImpl
  				)systemIdSystem);
  		}
  
  		private sealed class _IClosure4_69 : IClosure4
  		{
  			public _IClosure4_69(LocalObjectContainer _enclosing)
  			{
  				this._enclosing = _enclosing;
  			}
  
  			public object Run()
  			{
  				return this._enclosing.FreespaceManager();
  			}
  
  			private readonly LocalObjectContainer _enclosing;
  		}
  
  		public virtual IFreespaceManager FreespaceManager()
  		{
  			return _freespaceManager;
  		}
  
  		public virtual void BlockSizeReadFromFile(int size)
  		{
  			BlockSize(size);
  			SetRegularEndAddress(FileLength());
  		}
  
  		public virtual void SetRegularEndAddress(long address)
  		{
  			_blockEndAddress = _blockConverter.BytesToBlocks(address);
  		}
  
  		protected sealed override void Close2()
  		{
  			try
  			{
  				if (!_config.IsReadOnly())
  				{
  					CommitTransaction();
  					Shutdown();
  				}
  			}
  			finally
  			{
  				ShutdownObjectContainer();
  			}
  		}
  
  		public override void Commit1(Transaction trans)
  		{
  			trans.Commit();
  		}
  
  		internal virtual void ConfigureNewFile()
  		{
  			BlockSize(ConfigImpl.BlockSize());
  			_fileHeader = FileHeader.NewCurrentFileHeader();
  			SetRegularEndAddress(_fileHeader.Length());
  			NewSystemData(ConfigImpl.FreespaceSystem(), ConfigImpl.IdSystemType());
  			SystemData().ConverterVersion(Converter.Version);
  			CreateStringIO(_systemData.StringEncoding());
  			CreateIdSystem();
  			InitializeClassMetadataRepository();
  			InitalizeWeakReferenceSupport();
  			GenerateNewIdentity();
  			AbstractFreespaceManager blockedFreespaceManager = AbstractFreespaceManager.CreateNew
  				(this);
  			InstallFreespaceManager(blockedFreespaceManager);
  			InitNewClassCollection();
  			InitializeEssentialClasses();
  			_fileHeader.InitNew(this);
  			blockedFreespaceManager.Start(0);
  		}
  
  		private void NewSystemData(byte freespaceSystemType, byte idSystemType)
  		{
  			_systemData = new Db4objects.Db4o.Internal.SystemData();
  			_systemData.StringEncoding(ConfigImpl.Encoding());
  			_systemData.FreespaceSystem(freespaceSystemType);
  			_systemData.IdSystemType(idSystemType);
  		}
  
  		public override int ConverterVersion()
  		{
  			return _systemData.ConverterVersion();
  		}
  
  		public override long CurrentVersion()
  		{
  			return _timeStampIdGenerator.Last();
  		}
  
  		internal virtual void InitNewClassCollection()
  		{
  			// overridden in YapObjectCarrier to do nothing
  			ClassCollection().InitTables(1);
  		}
  
  		public BTree CreateBTreeClassIndex(int id)
  		{
  			return new BTree(_transaction, id, new IDHandler());
  		}
  
  		public AbstractQueryResult NewQueryResult(Transaction trans)
  		{
  			return NewQueryResult(trans, Config().EvaluationMode());
  		}
  
  		public sealed override AbstractQueryResult NewQueryResult(Transaction trans, QueryEvaluationMode
  			 mode)
  		{
  			if (trans == null)
  			{
  				throw new ArgumentNullException();
  			}
  			if (mode == QueryEvaluationMode.Immediate)
  			{
  				return new IdListQueryResult(trans);
  			}
  			return new HybridQueryResult(trans, mode);
  		}
  
  		public sealed override bool Delete4(Transaction transaction, ObjectReference @ref
  			, object obj, int cascade, bool userCall)
  		{
  			int id = @ref.GetID();
  			StatefulBuffer reader = ReadStatefulBufferById(transaction, id);
  			if (reader != null)
  			{
  				if (obj != null)
  				{
  					if ((!ShowInternalClasses()) && Const4.ClassInternal.IsAssignableFrom(obj.GetType
  						()))
  					{
  						return false;
  					}
  				}
  				reader.SetCascadeDeletes(cascade);
  				transaction.IdSystem().NotifySlotDeleted(id, SlotChangeFactory.UserObjects);
  				ClassMetadata classMetadata = @ref.ClassMetadata();
  				classMetadata.Delete(reader, obj);
  				return true;
  			}
  			return false;
  		}
  
  		public abstract long FileLength();
  
  		public abstract string FileName();
  
  		public virtual void Free(Slot slot)
  		{
  			if (slot.IsNull())
  			{
  				return;
  			}
  			// TODO: This should really be an IllegalArgumentException but old database files 
  			//       with index-based FreespaceManagers appear to deliver zeroed slots.
  			// throw new IllegalArgumentException();
  			if (_freespaceManager == null)
  			{
  				// Can happen on early free before freespacemanager
  				// is up, during conversion.
  				return;
  			}
  			if (DTrace.enabled)
  			{
  				DTrace.FileFree.LogLength(slot.Address(), slot.Length());
  			}
  			_freespaceManager.Free(slot);
  		}
  
  		public virtual void Free(int address, int a_length)
  		{
  			Free(new Slot(address, a_length));
  		}
  
  		public virtual void GenerateNewIdentity()
  		{
  			lock (_lock)
  			{
  				SetIdentity(Db4oDatabase.Generate());
  			}
  		}
  
  		public override AbstractQueryResult QueryAllObjects(Transaction trans)
  		{
  			return GetAll(trans, Config().EvaluationMode());
  		}
  
  		public virtual AbstractQueryResult GetAll(Transaction trans, QueryEvaluationMode 
  			mode)
  		{
  			AbstractQueryResult queryResult = NewQueryResult(trans, mode);
  			queryResult.LoadFromClassIndexes(ClassCollection().Iterator());
  			return queryResult;
  		}
  
  		public virtual int AllocatePointerSlot()
  		{
  			int id = AllocateSlot(Const4.PointerLength).Address();
  			if (!IsValidPointer(id))
  			{
  				return AllocatePointerSlot();
  			}
  			// write a zero pointer first
  			// to prevent delete interaction trouble
  			WritePointer(id, Slot.Zero);
  			if (DTrace.enabled)
  			{
  				DTrace.GetPointerSlot.Log(id);
  			}
  			return id;
  		}
  
  		protected virtual bool IsValidPointer(int id)
  		{
  			// We have to make sure that object IDs do not collide
  			// with built-in type IDs.
  			return !_handlers.IsSystemHandler(id);
  		}
  
  		public virtual Slot AllocateSlot(int length)
  		{
  			if (length <= 0)
  			{
  				throw new ArgumentException();
  			}
  			if (_freespaceManager != null && _freespaceManager.IsStarted())
  			{
  				Slot slot = _freespaceManager.AllocateSlot(length);
  				if (slot != null)
  				{
  					if (DTrace.enabled)
  					{
  						DTrace.GetSlot.LogLength(slot.Address(), slot.Length());
  					}
  					return slot;
  				}
  				while (GrowDatabaseByConfiguredSize())
  				{
  					slot = _freespaceManager.AllocateSlot(length);
  					if (slot != null)
  					{
  						if (DTrace.enabled)
  						{
  							DTrace.GetSlot.LogLength(slot.Address(), slot.Length());
  						}
  						return slot;
  					}
  				}
  			}
  			Slot appendedSlot = AppendBytes(length);
  			if (DTrace.enabled)
  			{
  				DTrace.GetSlot.LogLength(appendedSlot.Address(), appendedSlot.Length());
  			}
  			return appendedSlot;
  		}
  
  		private bool GrowDatabaseByConfiguredSize()
  		{
  			int reservedStorageSpace = ConfigImpl.DatabaseGrowthSize();
  			if (reservedStorageSpace <= 0)
  			{
  				return false;
  			}
  			int reservedBlocks = _blockConverter.BytesToBlocks(reservedStorageSpace);
  			int reservedBytes = _blockConverter.BlocksToBytes(reservedBlocks);
  			Slot slot = new Slot(_blockEndAddress, reservedBlocks);
  			if (Debug4.xbytes && Deploy.overwrite)
  			{
  				OverwriteDeletedBlockedSlot(slot);
  			}
  			else
  			{
  				WriteBytes(new ByteArrayBuffer(reservedBytes), _blockEndAddress, 0);
  			}
  			_freespaceManager.Free(_blockConverter.ToNonBlockedLength(slot));
  			_blockEndAddress += reservedBlocks;
  			return true;
  		}
  
  		public Slot AppendBytes(long bytes)
  		{
  			int blockCount = _blockConverter.BytesToBlocks(bytes);
  			int blockedStartAddress = _blockEndAddress;
  			int blockedEndAddress = _blockEndAddress + blockCount;
  			CheckBlockedAddress(blockedEndAddress);
  			_blockEndAddress = blockedEndAddress;
  			Slot slot = new Slot(blockedStartAddress, blockCount);
  			if (Debug4.xbytes && Deploy.overwrite)
  			{
  				OverwriteDeletedBlockedSlot(slot);
  			}
  			return _blockConverter.ToNonBlockedLength(slot);
  		}
  
  		private void CheckBlockedAddress(int blockedAddress)
  		{
  			if (blockedAddress < 0)
  			{
  				SwitchToReadOnlyMode();
  				throw new DatabaseMaximumSizeReachedException();
  			}
  		}
  
  		private void SwitchToReadOnlyMode()
  		{
  			_config.ReadOnly(true);
  		}
  
  		// When a file gets opened, it uses the file size to determine where 
  		// new slots can be appended. If this method would not be called, the
  		// freespace system could already contain a slot that points beyond
  		// the end of the file and this space could be allocated and used twice,
  		// for instance if a slot was allocated and freed without ever being
  		// written to file.
  		internal virtual void EnsureLastSlotWritten()
  		{
  			if (_blockEndAddress > _blockConverter.BytesToBlocks(FileLength()))
  			{
  				StatefulBuffer writer = CreateStatefulBuffer(SystemTransaction(), _blockEndAddress
  					 - 1, BlockSize());
  				writer.Write();
  			}
  		}
  
  		public override Db4oDatabase Identity()
  		{
  			return _systemData.Identity();
  		}
  
  		public virtual void SetIdentity(Db4oDatabase identity)
  		{
  			lock (Lock())
  			{
  				_systemData.Identity(identity);
  				// The dirty TimeStampIdGenerator triggers writing of
  				// the variable part of the systemdata. We need to
  				// make it dirty here, so the new identity is persisted:
  				_timeStampIdGenerator.Generate();
  				_fileHeader.WriteVariablePart(this);
  			}
  		}
  
  		internal override bool IsServer()
  		{
  			return i_isServer;
  		}
  
  		public sealed override int IdForNewUserObject(Transaction trans)
  		{
  			return trans.IdSystem().NewId(SlotChangeFactory.UserObjects);
  		}
  
  		public override void RaiseCommitTimestamp(long minimumVersion)
  		{
  			lock (Lock())
  			{
  				_timeStampIdGenerator.SetMinimumNext(minimumVersion);
  			}
  		}
  
  		public override StatefulBuffer ReadStatefulBufferById(Transaction a_ta, int a_id)
  		{
  			return ReadStatefulBufferById(a_ta, a_id, false);
  		}
  
  		public override ByteArrayBuffer[] ReadSlotBuffers(Transaction transaction, int[] 
  			ids)
  		{
  			ByteArrayBuffer[] buffers = new ByteArrayBuffer[ids.Length];
  			for (int i = 0; i < ids.Length; ++i)
  			{
  				if (ids[i] == 0)
  				{
  					buffers[i] = null;
  				}
  				else
  				{
  					buffers[i] = ReadBufferById(transaction, ids[i]);
  				}
  			}
  			return buffers;
  		}
  
  		public override ByteArrayBuffer ReadBufferById(Transaction trans, int id)
  		{
  			return ReadBufferById(trans, id, false);
  		}
  
  		public sealed override ByteArrayBuffer ReadBufferById(Transaction trans, int id, 
  			bool lastCommitted)
  		{
  			if (id <= 0)
  			{
  				throw new ArgumentException();
  			}
  			Slot slot = lastCommitted ? trans.IdSystem().CommittedSlot(id) : trans.IdSystem()
  				.CurrentSlot(id);
  			if (DTrace.enabled)
  			{
  				DTrace.SlotRead.LogLength(id, slot);
  			}
  			return ReadBufferBySlot(slot);
  		}
  
  		public override StatefulBuffer ReadStatefulBufferById(Transaction trans, int id, 
  			bool lastCommitted)
  		{
  			if (id <= 0)
  			{
  				throw new ArgumentException();
  			}
  			Slot slot = lastCommitted ? trans.IdSystem().CommittedSlot(id) : trans.IdSystem()
  				.CurrentSlot(id);
  			if (DTrace.enabled)
  			{
  				DTrace.SlotRead.LogLength(id, slot);
  			}
  			return ReadStatefulBufferBySlot(trans, id, slot);
  		}
  
  		public virtual ByteArrayBuffer ReadBufferBySlot(Slot slot)
  		{
  			if (Slot.IsNull(slot))
  			{
  				return null;
  			}
  			if (DTrace.enabled)
  			{
  				DTrace.ReadSlot.LogLength(slot.Address(), slot.Length());
  			}
  			ByteArrayBuffer buffer = new ByteArrayBuffer(slot.Length());
  			buffer.ReadEncrypt(this, slot.Address());
  			return buffer;
  		}
  
  		public virtual StatefulBuffer ReadStatefulBufferBySlot(Transaction trans, int id, 
  			Slot slot)
  		{
  			if (Slot.IsNull(slot))
  			{
  				return null;
  			}
  			if (DTrace.enabled)
  			{
  				DTrace.ReadSlot.LogLength(slot.Address(), slot.Length());
  			}
  			StatefulBuffer buffer = CreateStatefulBuffer(trans, slot.Address(), slot.Length()
  				);
  			buffer.SetID(id);
  			buffer.ReadEncrypt(this, slot.Address());
  			return buffer;
  		}
  
  		protected override bool DoFinalize()
  		{
  			return _fileHeader != null;
  		}
  
  		/// <exception cref="Db4objects.Db4o.Ext.OldFormatException"></exception>
  		internal virtual void ReadThis()
  		{
  			NewSystemData(AbstractFreespaceManager.FmLegacyRam, StandardIdSystemFactory.Legacy
  				);
  			BlockSizeReadFromFile(1);
  			_fileHeader = FileHeader.Read(this);
  			if (Config().GenerateCommitTimestamps().IsUnspecified())
  			{
  				Config().GenerateCommitTimestamps(_systemData.IdToTimestampIndexId() != 0);
  			}
  			CreateStringIO(_systemData.StringEncoding());
  			CreateIdSystem();
  			InitializeClassMetadataRepository();
  			InitalizeWeakReferenceSupport();
  			SetNextTimeStampId(SystemData().LastTimeStampID());
  			ClassCollection().SetID(_systemData.ClassCollectionID());
  			ClassCollection().Read(SystemTransaction());
  			Converter.Convert(new ConversionStage.ClassCollectionAvailableStage(this));
  			_fileHeader.ReadIdentity(this);
  			if (_config.IsReadOnly())
  			{
  				return;
  			}
  			if (!ConfigImpl.CommitRecoveryDisabled())
  			{
  				_fileHeader.CompleteInterruptedTransaction(this);
  			}
  			IFreespaceManager blockedFreespaceManager = AbstractFreespaceManager.CreateNew(this
  				, _systemData.FreespaceSystem());
  			InstallFreespaceManager(blockedFreespaceManager);
  			blockedFreespaceManager.Read(this, _systemData.InMemoryFreespaceSlot());
  			blockedFreespaceManager.Start(_systemData.BTreeFreespaceId());
  
gt: 			_fileHeader = _fileHeader.Convert(this);