imgtools/romtools/rombuild/r_dir.cpp
changeset 590 360bd6b35136
parent 0 044383f39525
--- a/imgtools/romtools/rombuild/r_dir.cpp	Wed Jun 16 16:51:40 2010 +0300
+++ b/imgtools/romtools/rombuild/r_dir.cpp	Wed Jun 23 16:56:47 2010 +0800
@@ -1,807 +1,818 @@
-/*
-* Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
-* All rights reserved.
-* This component and the accompanying materials are made available
-* under the terms of the License "Eclipse Public License v1.0"
-* which accompanies this distribution, and is available
-* at the URL "http://www.eclipse.org/legal/epl-v10.html".
-*
-* Initial Contributors:
-* Nokia Corporation - initial contribution.
-*
-* Contributors:
-*
-* Description: 
-* e32tools/rombuild/r_dir.cpp
-*
-*/
-
-
-#include <stdlib.h>
-#include <string.h>
-#include "r_dir.h"
-#include "r_obey.h"
-#include "r_rom.h"
-#include "r_global.h"
-
-// Generalised set handling
-
-// class SetMember
-TInt SetMember::TotalInSystem=0;
-
-SetMember::~SetMember()
-	{
-	TotalInSystem--;
-	TRACE(TDIR,Print(EAlways,"SetMember %08x Destruct Remaining=%d\n",this,TotalInSystem));
-	}
-
-void SetMember::Close()
-	{
-	delete this;
-	}
-
-// class FiniteSet
-FiniteSet::FiniteSet(TInt aMaxCount)
-	: SetMember(EFiniteSetType), iMaxCount(aMaxCount), iCount(0), iMembers(NULL)
-	{}
-
-FiniteSet::FiniteSet(const FiniteSet& aSet)
-	: SetMember(aSet), iMaxCount(aSet.iMaxCount), iCount(0), iMembers(NULL)
-	{}
-
-FiniteSet* FiniteSet::New(TInt aMaxCount)
-	{
-	FiniteSet* pS=new FiniteSet(aMaxCount);
-	if (pS)
-		pS=pS->Construct();
-	return pS;
-	}
-
-FiniteSet* FiniteSet::Construct()
-	{
-	SetMember** pM=new SetMember*[iMaxCount];
-	if (!pM)
-		{
-		delete this;
-		return NULL;
-		}
-	iMembers=pM;
-	TInt i;
-	for(i=0; i<iMaxCount; i++)
-		pM[i]=NULL;
-	return this;
-	}
-
-FiniteSet* FiniteSet::Singleton(TInt aMaxCount, const SetMember& aMember)
-	{
-	FiniteSet* pS=New(aMaxCount);
-	if (pS)
-		{
-		pS->iCount=1;
-		pS->iMembers[0]=(SetMember*)&aMember;
-		}
-	return pS;
-	}
-
-FiniteSet::~FiniteSet()
-	{
-	TRACE(TDIR,Print(EAlways,"FiniteSet %08x Destruct, iCount=%d\n",this,iCount));
-	TInt i;
-	for (i=0; i<iCount; i++)
-		iMembers[i]->Close();
-	delete[] iMembers;
-	}
-
-TInt FiniteSet::Find(const SetMember& aMember, TInt& anIndex) const
-	{
-	if (iCount==0)
-		{
-		anIndex=0;
-		return KErrNotFound;
-		}
-	TInt k=aMember.Compare(*iMembers[0]);
-	if (k==0)
-		{
-		anIndex=0;
-		return KErrNone;
-		}
-	if (k<0)
-		{
-		anIndex=0;
-		return KErrNotFound;
-		}
-	if (iCount==1)
-		{
-		anIndex=1;
-		return KErrNotFound;
-		}
-	TInt r=iCount-1;
-	k=aMember.Compare(*iMembers[r]);
-	if (k==0)
-		{
-		anIndex=r;
-		return KErrNone;
-		}
-	if (k>0)
-		{
-		anIndex=iCount;
-		return KErrNotFound;
-		}
-	if (iCount==2)
-		{
-		anIndex=1;
-		return KErrNotFound;
-		}
-	TInt l=0;
-	while(r-l>1)
-		{
-		TInt m=(l+r)>>1;
-		k=aMember.Compare(*iMembers[m]);
-		if (k==0)
-			{
-			anIndex=m;
-			return KErrNone;
-			}
-		if (k>0)
-			l=m;
-		else
-			r=m;
-		}
-	anIndex=r;
-	return KErrNotFound;
-	}
-
-TInt FiniteSet::Compare(const SetMember& aSetMember) const
-	{
-	TInt k=Type()-aSetMember.Type();
-	if (k!=0)
-		return k;
-	const FiniteSet& s=(const FiniteSet&)aSetMember;
-	TInt c=Min(iCount,s.iCount);
-	TInt i;
-	for(i=0; i<c; i++)
-		{
-		k=iMembers[i]->Compare(s[i]);
-		if (k!=0)
-			return k;
-		}
-	return (iCount-s.iCount);
-	}
-
-SetMember* FiniteSet::Copy() const
-	{
-	FiniteSet* pS=new FiniteSet(*this);
-	if (pS)
-		{
-		SetMember** pA=new SetMember*[iMaxCount];
-		if (!pA)
-			{
-			delete pS;
-			return NULL;
-			}
-		pS->iMembers=pA;
-		TInt i;
-		for(i=0; i<iCount; i++)
-			{
-			SetMember* pM=iMembers[i]->Copy();
-			if (!pM)
-				{
-				delete pS;
-				return NULL;
-				}
-			pA[i]=pM;
-			pS->iCount++;
-			}
-		}
-	return pS;
-	}
-
-TInt FiniteSet::Find(const SetMember& aMember) const
-	{
-	TInt i;
-	TInt r=Find(aMember,i);
-	if (r<0)
-		return r;
-	return i;
-	}
-
-TBool FiniteSet::SubsetOf(const FiniteSet& aSet) const
-	{
-	if (iCount>aSet.iCount)
-		return EFalse;
-	TInt i;
-	for(i=0; i<iCount; i++)
-		{
-		TInt j;
-		if (aSet.Find(*iMembers[i],j)!=KErrNone)
-			return EFalse;
-		}
-	return ETrue;
-	}
-
-TInt FiniteSet::Intersection(const FiniteSet& aSet)
-	{
-	if (iCount==0)
-		return KErrNotFound;
-	TInt i;
-	for(i=0; i<iCount; i++)
-		{
-		TInt j;
-		if (aSet.Find(*iMembers[i],j)!=KErrNone)
-			Detach(i)->Close();
-		}
-	return iCount ? KErrNone : KErrNotFound;
-	}
-
-TInt FiniteSet::Union(const FiniteSet& aSet)
-	{
-	TInt i;
-	for(i=0; i<aSet.iCount; i++)
-		{
-		const SetMember& m=*aSet.iMembers[i];
-		TInt j;
-		if (Find(m,j)!=KErrNone)
-			{
-			const SetMember* pC=m.Copy();
-			if (!pC)
-				return KErrNoMemory;
-			TInt r=Insert(*pC,j);
-			if (r!=KErrNone)
-				return r;
-			}
-		}
-	return iCount ? KErrNone : KErrNotFound;
-	}
-
-TInt FiniteSet::Difference(const FiniteSet& aSet)
-	{
-	if (iCount==0)
-		return KErrNotFound;
-	TInt i;
-	for(i=0; i<iCount; i++)
-		{
-		TInt j;
-		if (aSet.Find(*iMembers[i],j)==KErrNone)
-			Detach(i)->Close();
-		}
-	return iCount ? KErrNone : KErrNotFound;
-	}
-
-TInt FiniteSet::Add(const SetMember& aMember)
-	{
-	TInt i;
-	TInt r=Find(aMember,i);
-	if (r==KErrNotFound && Insert(aMember,i)==KErrOverflow)
-		return KErrOverflow;
-	return r;
-	}
-
-TInt FiniteSet::Remove(const SetMember& aMember)
-	{
-	TInt i;
-	TInt r=Find(aMember,i);
-	if (r==KErrNone)
-		Detach(i)->Close();
-	return r;
-	}
-
-SetMember* FiniteSet::Detach(TInt anIndex)
-	{
-	TInt i;
-	SetMember* pM=iMembers[anIndex];
-	for(i=anIndex; i<iCount-1; i++)
-		iMembers[i]=iMembers[i+1];
-	iCount--;
-	return pM;
-	}
-
-TInt FiniteSet::Insert(const SetMember& aMember, TInt anIndex)
-	{
-	if (iCount==iMaxCount)
-		return KErrOverflow;
-	TInt i;
-	for(i=iCount-1; i>=anIndex; i--)
-		iMembers[i+1]=iMembers[i];
-	iMembers[anIndex]=(SetMember*)&aMember;
-	iCount++;
-	return KErrNone;
-	}
-
-// ROMBUILD-specific stuff
-
-inline TLinAddr ActualToRomAddress(TAny* anAddr)
-	{ return TLinAddr(anAddr)-TheRomMem+TheRomLinearAddress; }
-
-// class TVariantList
-TInt TVariantList::NumVariants;
-THardwareVariant TVariantList::Variants[TVariantList::EMaxVariants];
-void TVariantList::Setup(CObeyFile* aObey)
-	{
-	NumVariants=aObey->iNumberOfVariants;
-	if (NumVariants>EMaxVariants)
-		Print(EError,"Too many variants");
-	TInt i;
-	for(i=0; i<NumVariants; i++)
-		{
-		Variants[i]=aObey->iVariants[i]->iHardwareVariant;
-		}
-	}
-
-TVariantList::TVariantList(THardwareVariant a)
-	{
-	iList=0;
-	TInt i;
-	for (i=0; i<NumVariants; i++)
-		{
-		if (Variants[i]<=a)
-			iList|=TUint(1<<i);
-		}
-	}
-
-THardwareVariant TVariantList::Lookup() const
-	{
-	TInt i;
-	for (i=0; i<NumVariants; i++)
-		{
-		if (iList & TUint(1<<i))
-			return Variants[i];
-		}
-	return THardwareVariant(0);
-	}
-
-void TVariantList::SetNumVariants(TInt aNumVariants)
-{
-	NumVariants = aNumVariants;
-	if (NumVariants>EMaxVariants)
-		Print(EError,"Too many variants");
-}
-
-void TVariantList::SetVariants(THardwareVariant* aVariants)
-{
-	TInt Index = NumVariants;
-	while(Index--)
-	{
-		Variants[Index] = aVariants[Index];
-	}
-}
-
-void DumpRomEntry(const TRomEntry& e)
-	{
-	char name[256];
-	char* d = name;
-	const wchar_t* s = (const wchar_t*)e.iName;
-	const wchar_t* sE = s + e.iNameLength;
-	for (; s<sE; *d++ = (char)*s++) {}
-	*d++ = 0;
-	Print(ELog, "RomEntry @ %08x: SZ=%08x A=%08x att=%02x name %s\n", &e, e.iSize, e.iAddressLin, e.iAtt, name);
-	}
-
-// class Entry
-TRomEntry* Entry::CreateRomEntry(char*& anAddr) const
-	{
-
-	TRomEntry *pE=(TRomEntry*)anAddr;
-	pE->iAtt=iRomNode->iAtt;
-	pE->iSize=iRomNode->iRomFile->iAddresses.iSize;
-	pE->iAddressLin=iRomNode->iRomFile->iAddresses.iRunAddr;
-	if (IsFile())
-		iRomNode->iRomFile->SetRomEntry(pE);
-	pE->iName[0]=0;
-	pE->iName[1]=0;
-	TInt nl=iRomNode->NameCpy((char*)pE->iName);
-	pE->iNameLength=(TUint8)nl;
-	if (Unicode)
-		nl<<=1;
-	anAddr+=Align4(KRomEntrySize+nl);
-	TRACE(TDIR,DumpRomEntry(*pE));
-	return pE;
-	}
-
-const TText* Entry::Name() const
-	{
-	return iRomNode->iName;
-	}
-
-// class FileEntry
-FileEntry::FileEntry(const FileEntry& aFileEntry)
-	: Entry(aFileEntry)
-	{
-	iVariants=aFileEntry.iVariants;
-	iRomNode=aFileEntry.iRomNode;
-	}
-
-FileEntry* FileEntry::New(TRomNode* aFile)
-	{
-	FileEntry* pE=new FileEntry();
-	if (pE)
-		{
-		pE->iRomNode=aFile;
-		pE->iVariants=TVariantList(aFile->HardwareVariant());
-		}
-	return pE;
-	}
-
-TInt FileEntry::Compare(const SetMember& aMember) const
-	{
-	TInt k=Type()-aMember.Type();
-	if (k!=0)
-		return k;
-	FileEntry *entry=(FileEntry *)&aMember;
-	return (iRomNode->iIdentifier-entry->iRomNode->iIdentifier);
-	}
-
-SetMember* FileEntry::Copy() const
-	{
-	return new FileEntry(*this);
-	}
-
-FileEntry::~FileEntry()
-	{
-	}
-
-// class DirEntry
-DirEntry::DirEntry(const DirEntry& aDirEntry)
-	: Entry(aDirEntry)
-	{
-	iVariants=aDirEntry.iVariants;
-	iRomNode=aDirEntry.iRomNode;
-	iDir=aDirEntry.iDir;
-	}
-
-DirEntry* DirEntry::New(TRomNode* aFile, Directory* aDir)
-	{
-	DirEntry* pE=new DirEntry();
-	if (pE)
-		{
-		pE->iRomNode=aFile;
-		pE->iVariants=aDir->iVariants;
-		pE->iDir=aDir;
-		if (aDir)
-			aDir->Open();
-		}
-	return pE;
-	}
-
-TInt DirEntry::Compare(const SetMember& aMember) const
-	{
-	TInt k=Type()-aMember.Type();
-	if (k!=0)
-		return k;
-	DirEntry *entry=(DirEntry *)&aMember;
-	return (iDir->iIdentifier - entry->iDir->iIdentifier);
-	}
-
-SetMember* DirEntry::Copy() const
-	{
-	DirEntry* pE=new DirEntry(*this);
-	if (pE && pE->iDir)
-		pE->iDir->Open();
-	return pE;
-	}
-
-DirEntry::~DirEntry()
-	{
-	if (iDir)
-		iDir->Close();
-	}
-
-// data structure and function for qsort
-struct SortableEntry 
-	{
-	unsigned int iOffset;
-	Entry* iEntry;
-	};
-
-int compare(const void* left, const void* right)
-	{
-	const SortableEntry* le  = (const SortableEntry*)left;
-	const SortableEntry* re = (const SortableEntry*)right;
-	if (le->iEntry->IsDir())
-		{
-		if (!re->iEntry->IsDir())
-			return -1;	// dir < file
-		}
-	else
-		{
-		if (re->iEntry->IsDir())
-			return +1;	// file > dir
-		}
-	// Both the same type of entry, sort by name.
-	// Sorting the 8-bit data using ASCII folding matches the sort order in terms of 16 bit
-	// characters provided that 8-bit data is actually CESU-8 rather than UTF-8. The two
-	// formats differ only when using surrogates (ie unicode values >= 0x10000). UTF-8 encodes
-	// an entire 32 bit value as a sequence of up to 6 bytes whereas CESU-8 encodes UTF-16
-	// values independently.
-	const char* l = (const char*)le->iEntry->Name();
-	const char* r = (const char*)re->iEntry->Name();
-	int result, lc, rc;
-	do	{
-		lc = *l++;
-		rc = *r++;
-		if (lc >= 'A' && lc <= 'Z')
-			lc += ('a' - 'A');
-		if (rc >= 'A' && rc <= 'Z')
-			rc += ('a' - 'A');
-		result = lc - rc;
-		} while (lc && result==0);
-	return result;
-	}
-
-
-TRomDir* DirEntry::CreateRomEntries(char*& anAddr) const
-	{
-	TInt i;
-	TInt count=iDir->Count();
-	TInt subdircount=0;
-	for(i=0; i<count; i++)
-		{
-		Entry* pE=(Entry*)&(*iDir)[i];
-		if (pE->IsDir())
-			{
-			subdircount++;
-			// Recursively build & place the subdirectories
-			DirEntry *pD=(DirEntry*)pE;
-			TRomDir *pR=pD->iDir->iRomDir;
-			if (!pR)
-				{
-				pR=pD->CreateRomEntries(anAddr);
-				pD->iDir->iRomDir=pR;
-				}
-			}
-		}
-	// Now place & build the TRomDir for this directory
-	TInt *pS=(TInt*)anAddr;
-	iDir->iRomDir=(TRomDir*)anAddr;
-	*pS=0;
-	anAddr+=sizeof(TInt);
-
-	char* offsetbase=anAddr;
-	SortableEntry* array=new SortableEntry [count];
-	if (array==0)
-		{
-		Print(EError,"Failed to allocate array of SortableEntry\n");
-		exit(-1);
-		}
-
-	for(i=0; i<count; i++)
-		{
-		Entry* pE=(Entry*)&(*iDir)[i];
-		array[i].iOffset=anAddr-offsetbase;
-		array[i].iEntry=pE;
-		TRomEntry *pR=pE->CreateRomEntry(anAddr);
-		if (pE->IsDir())
-			{
-			TRomDir *pD=((DirEntry*)pE)->iDir->iRomDir;
-			if (pD)
-				pR->iAddressLin=ActualToRomAddress(pD);
-			else
-				Print(EError,"Failed to fix up subdirectory address\n");
-			}
-		}
-	*pS=TInt(anAddr-(char*)pS-sizeof(TInt));
-
-	// Emit table of offsets for the subdirs and files in sorted order
-	if (gSortedRomFs)
-		{
-	TInt filecount=count-subdircount;
-	if (filecount>65535 || subdircount>65535)
-		{
-		Print(EError,"Too many files or subdirectories\n");
-		exit(-1);
-		}
-	TUint16* ptr=(TUint16*)anAddr;
-	*ptr++=(TUint16)subdircount;
-	*ptr++=(TUint16)filecount;
-	qsort(array,count,sizeof(SortableEntry),&compare);
-	for (i=0; i<count; i++)
-		{
-		unsigned int scaledOffset = array[i].iOffset>>2;
-		if ((array[i].iOffset & 3) != 0 || scaledOffset > 65535)
-			Print(EError, "Bad offset into directory\n");
-		*ptr++ = (TUint16)scaledOffset;
-		}
-	anAddr=(char*)ALIGN4((int)ptr);
-		}
-	delete [] array;
-	return (TRomDir*)pS;
-	}
-
-// class Directory
-TInt Directory::DirectoryCount=0;
-Directory::Directory(TInt aMaxCount)
-	: FiniteSet(aMaxCount), iRomDir(NULL), iAccessCount(1)
-	{
-	iIdentifier=Directory::DirectoryCount++;
-	}
-
-Directory* Directory::New(TInt aMaxCount, TVariantList aList)
-	{
-	Directory *pD=new Directory(aMaxCount);
-	if (pD)
-		{
-		pD->iVariants=aList;
-		pD=(Directory*)pD->Construct();
-		}
-	return pD;
-	}
-
-Directory::~Directory()
-	{
-	TRACE(TDIR,Print(EAlways,"Directory %08x Destruct\n",this));
-	}
-
-void Directory::Open()
-	{
-	iAccessCount++;
-	TRACE(TDIR,Print(EAlways,"Directory %08x Open() access count=%d\n",this,iAccessCount));
-	}
-
-void Directory::Close()
-	{
-	TRACE(TDIR,Print(EAlways,"Directory %08x Close() access count=%d\n",this,iAccessCount));
-	if (--iAccessCount==0)
-		delete this;
-	}
-
-TInt Directory::Compile(const FiniteSet& aSet)
-	{
-	TInt i;
-	TInt count=aSet.Count();
-	for(i=0; i<count; i++)
-		{
-		Entry *pE=(Entry*)&aSet[i];
-		if (iVariants<=pE->Variants())
-			{
-			Entry *pN=(Entry*)pE->Copy();
-			if (!pN)
-				return KErrNoMemory;
-			pN->Restrict(iVariants);
-			TInt r=Add(*pN);
-			if (r==KErrOverflow)
-				return r;
-			}
-		}
-	return KErrNone;
-	}
-
-TInt Directory::Merge(const Directory& aDir)
-	{
-	TInt i;
-	TInt r=Find(aDir,i);
-	if (r==KErrNone)
-		{
-		((Directory*)iMembers[i])->iVariants.Union(aDir.iVariants);
-		return KErrAlreadyExists;
-		}
-	else if (Insert(aDir,i)==KErrOverflow)
-		return KErrOverflow;
-	return KErrNone;
-	}
-
-// class RomFileStructure
-RomFileStructure::RomFileStructure(TInt aMaxCount)
-	: FiniteSet(aMaxCount)
-	{}
-
-RomFileStructure::~RomFileStructure()
-	{
-	}
-
-RomFileStructure* RomFileStructure::New(TInt aMaxCount)
-	{
-	RomFileStructure* pS=new RomFileStructure(aMaxCount);
-	if (pS)
-		pS=(RomFileStructure*)pS->Construct();
-	return pS;
-	}
-
-void RomFileStructure::Destroy()
-	{
-	}
-
-TInt RomFileStructure::ProcessDirectory(TRomNode* aDir)
-	{
-	TRACE(TSCRATCH, Print(EAlways, "ProcessDirectory (%08x) %s\n",aDir,aDir->iName));
-	TRACE(TDIR,Print(EAlways,"ProcessDirectory %s\nInitial:\n",aDir->iName));
-	TRACE(TDIR,DebugPrint());
-	TInt dirs=0;
-	TInt files=0;
-	aDir->CountDirectory(files,dirs);
-	TInt maxSize=files+dirs*TVariantList::NumVariants;
-	TRACE(TDIR,Print(EAlways,"files=%d dirs=%d maxSize=%d\n",files,dirs,maxSize));
-	RomFileStructure* pS=New(maxSize);
-	if (!pS)
-		return KErrNoMemory;
-	TInt r=aDir->ProcessDirectory(pS);
-	TRACE(TDIR,Print(EAlways,"FileList:\n"));
-	TRACE(TDIR,pS->DebugPrint());
-	Directory* dir[TVariantList::EMaxVariants];
-	TInt v;
-	for(v=0; v<TVariantList::NumVariants; v++)
-		{
-		TVariantList vList(v);
-		Directory *pD=Directory::New(files+dirs,vList);
-		if (!pD)
-			return KErrNoMemory;
-		dir[v]=pD;
-		r=pD->Compile(*pS);
-		if (r!=KErrNone)
-			return r;
-		TRACE(TDIR,Print(EAlways,"Variant %d Directory:\n",v));
-		TRACE(TDIR,pD->DebugPrint());
-		}
-	pS->Close();
-	Directory *pX=Directory::New(TVariantList::NumVariants,TVariantList());
-	if (!pX)
-		return KErrNoMemory;
-	for(v=0; v<TVariantList::NumVariants; v++)
-		{
-		if (dir[v]->Empty())
-			r=KErrAlreadyExists;
-		else
-			r=pX->Merge(*dir[v]);
-		if (r==KErrAlreadyExists)
-			{
-			dir[v]->Close();
-			dir[v]=NULL;
-			}
-		else if (r!=KErrNone)
-			return r;
-		}
-	TRACE(TDIR,Print(EAlways,"Final Directories:\n",v));
-	TRACE(TDIR,pX->DebugPrint());
-	TInt count=pX->Count();
-	TInt i;
-	for(i=0; i<count; i++)
-		{
-		Directory* pD=(Directory*)&(*pX)[i];
-		DirEntry* pE=DirEntry::New(aDir,pD);
-		if (!pE)
-			return KErrNoMemory;
-		r=Add(*pE);	// accumulate into the caller
-		if (r==KErrOverflow)
-			return r;
-		}
-	pX->Close();
-	return KErrNone;
-	}
-
-
-// DEBUG
-
-void FileEntry::DebugPrint() const
-	{
-	Print(EAlways,"FileEntry %08x %08x %s\n",iRomNode,iVariants.Mask(),iRomNode->iName);
-	}
-
-void DirEntry::DebugPrint() const
-	{
-	Print(EAlways,"DirEntry %08x %08x %08x %s\n",iRomNode,iVariants.Mask(),iDir,iRomNode->iName);
-	}
-
-void FiniteSet::DebugPrint() const
-	{
-	if (Count()==0)
-		Print(EAlways,"FiniteSet 0\n");
-	else
-		{
-		Print(EAlways,"FiniteSet %d {\n",Count());
-		TInt i;
-		for (i=0; i<Count(); i++)
-			{
-			iMembers[i]->DebugPrint();
-			}
-		Print(EAlways,"}\n");
-		}
-	}
-
-void Directory::DebugPrint() const
-	{
-	Print(EAlways,"Directory %08x %08x\n",iVariants.Mask(),iRomDir);
-	FiniteSet::DebugPrint();
-	}
-
-
-
-
-
+/*
+* Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of the License "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: 
+* e32tools/rombuild/r_dir.cpp
+*
+*/
+
+
+#include <stdlib.h>
+#include <string.h>
+#include "r_dir.h"
+#include "r_obey.h"
+#include "r_rom.h"
+#include "r_global.h"
+#include "utf16string.h"
+
+// Generalised set handling
+
+// class SetMember
+TInt SetMember::TotalInSystem=0;
+
+SetMember::~SetMember()
+	{
+	TotalInSystem--;
+	TRACE(TDIR,Print(EAlways,"SetMember %08x Destruct Remaining=%d\n",this,TotalInSystem));
+	}
+
+void SetMember::Close()
+	{
+	delete this;
+	}
+
+// class FiniteSet
+FiniteSet::FiniteSet(TInt aMaxCount)
+	: SetMember(EFiniteSetType), iMaxCount(aMaxCount), iCount(0), iMembers(NULL)
+	{}
+
+FiniteSet::FiniteSet(const FiniteSet& aSet)
+	: SetMember(aSet), iMaxCount(aSet.iMaxCount), iCount(0), iMembers(NULL)
+	{}
+
+FiniteSet* FiniteSet::New(TInt aMaxCount)
+	{
+	FiniteSet* pS=new FiniteSet(aMaxCount);
+	if (pS)
+		pS=pS->Construct();
+	return pS;
+	}
+
+FiniteSet* FiniteSet::Construct()
+	{
+	SetMember** pM=new SetMember*[iMaxCount];
+	if (!pM)
+		{
+		delete this;
+		return NULL;
+		}
+	iMembers=pM;
+	TInt i;
+	for(i=0; i<iMaxCount; i++)
+		pM[i]=NULL;
+	return this;
+	}
+
+FiniteSet* FiniteSet::Singleton(TInt aMaxCount, const SetMember& aMember)
+	{
+	FiniteSet* pS=New(aMaxCount);
+	if (pS)
+		{
+		pS->iCount=1;
+		pS->iMembers[0]=(SetMember*)&aMember;
+		}
+	return pS;
+	}
+
+FiniteSet::~FiniteSet()
+	{
+	TRACE(TDIR,Print(EAlways,"FiniteSet %08x Destruct, iCount=%d\n",this,iCount));
+	TInt i;
+	for (i=0; i<iCount; i++)
+		iMembers[i]->Close();
+	if(iMembers)
+		delete[] iMembers;
+	}
+
+TInt FiniteSet::Find(const SetMember& aMember, TInt& anIndex) const
+	{
+	if (iCount==0)
+		{
+		anIndex=0;
+		return KErrNotFound;
+		}
+	TInt k=aMember.Compare(*iMembers[0]);
+	if (k==0)
+		{
+		anIndex=0;
+		return KErrNone;
+		}
+	if (k<0)
+		{
+		anIndex=0;
+		return KErrNotFound;
+		}
+	if (iCount==1)
+		{
+		anIndex=1;
+		return KErrNotFound;
+		}
+	TInt r=iCount-1;
+	k=aMember.Compare(*iMembers[r]);
+	if (k==0)
+		{
+		anIndex=r;
+		return KErrNone;
+		}
+	if (k>0)
+		{
+		anIndex=iCount;
+		return KErrNotFound;
+		}
+	if (iCount==2)
+		{
+		anIndex=1;
+		return KErrNotFound;
+		}
+	TInt l=0;
+	while(r-l>1)
+		{
+		TInt m=(l+r)>>1;
+		k=aMember.Compare(*iMembers[m]);
+		if (k==0)
+			{
+			anIndex=m;
+			return KErrNone;
+			}
+		if (k>0)
+			l=m;
+		else
+			r=m;
+		}
+	anIndex=r;
+	return KErrNotFound;
+	}
+
+TInt FiniteSet::Compare(const SetMember& aSetMember) const
+	{
+	TInt k=Type()-aSetMember.Type();
+	if (k!=0)
+		return k;
+	const FiniteSet& s=(const FiniteSet&)aSetMember;
+	TInt c=Min(iCount,s.iCount);
+	TInt i;
+	for(i=0; i<c; i++)
+		{
+		k=iMembers[i]->Compare(s[i]);
+		if (k!=0)
+			return k;
+		}
+	return (iCount-s.iCount);
+	}
+
+SetMember* FiniteSet::Copy() const
+	{
+	FiniteSet* pS=new FiniteSet(*this);
+	if (pS)
+		{
+		SetMember** pA=new SetMember*[iMaxCount];
+		if (!pA)
+			{
+			delete pS;
+			return NULL;
+			}
+		pS->iMembers=pA;
+		TInt i;
+		for(i=0; i<iCount; i++)
+			{
+			SetMember* pM=iMembers[i]->Copy();
+			if (!pM)
+				{
+				delete pS;
+				return NULL;
+				}
+			pA[i]=pM;
+			pS->iCount++;
+			}
+		}
+	return pS;
+	}
+
+TInt FiniteSet::Find(const SetMember& aMember) const
+	{
+	TInt i;
+	TInt r=Find(aMember,i);
+	if (r<0)
+		return r;
+	return i;
+	}
+
+TBool FiniteSet::SubsetOf(const FiniteSet& aSet) const
+	{
+	if (iCount>aSet.iCount)
+		return EFalse;
+	TInt i;
+	for(i=0; i<iCount; i++)
+		{
+		TInt j;
+		if (aSet.Find(*iMembers[i],j)!=KErrNone)
+			return EFalse;
+		}
+	return ETrue;
+	}
+
+TInt FiniteSet::Intersection(const FiniteSet& aSet)
+	{
+	if (iCount==0)
+		return KErrNotFound;
+	TInt i;
+	for(i=0; i<iCount; i++)
+		{
+		TInt j;
+		if (aSet.Find(*iMembers[i],j)!=KErrNone)
+			Detach(i)->Close();
+		}
+	return iCount ? KErrNone : KErrNotFound;
+	}
+
+TInt FiniteSet::Union(const FiniteSet& aSet)
+	{
+	TInt i;
+	for(i=0; i<aSet.iCount; i++)
+		{
+		const SetMember& m=*aSet.iMembers[i];
+		TInt j;
+		if (Find(m,j)!=KErrNone)
+			{
+			const SetMember* pC=m.Copy();
+			if (!pC)
+				return KErrNoMemory;
+			TInt r=Insert(*pC,j);
+			if (r!=KErrNone)
+				return r;
+			}
+		}
+	return iCount ? KErrNone : KErrNotFound;
+	}
+
+TInt FiniteSet::Difference(const FiniteSet& aSet)
+	{
+	if (iCount==0)
+		return KErrNotFound;
+	TInt i;
+	for(i=0; i<iCount; i++)
+		{
+		TInt j;
+		if (aSet.Find(*iMembers[i],j)==KErrNone)
+			Detach(i)->Close();
+		}
+	return iCount ? KErrNone : KErrNotFound;
+	}
+
+TInt FiniteSet::Add(const SetMember& aMember)
+	{
+	TInt i;
+	TInt r=Find(aMember,i);
+	if (r==KErrNotFound && Insert(aMember,i)==KErrOverflow)
+		return KErrOverflow;
+	return r;
+	}
+
+TInt FiniteSet::Remove(const SetMember& aMember)
+	{
+	TInt i;
+	TInt r=Find(aMember,i);
+	if (r==KErrNone)
+		Detach(i)->Close();
+	return r;
+	}
+
+SetMember* FiniteSet::Detach(TInt anIndex)
+	{
+	TInt i;
+	SetMember* pM=iMembers[anIndex];
+	for(i=anIndex; i<iCount-1; i++)
+		iMembers[i]=iMembers[i+1];
+	iCount--;
+	return pM;
+	}
+
+TInt FiniteSet::Insert(const SetMember& aMember, TInt anIndex)
+	{
+	if (iCount==iMaxCount)
+		return KErrOverflow;
+	TInt i;
+	for(i=iCount-1; i>=anIndex; i--)
+		iMembers[i+1]=iMembers[i];
+	iMembers[anIndex]=(SetMember*)&aMember;
+	iCount++;
+	return KErrNone;
+	}
+
+// ROMBUILD-specific stuff
+
+inline TLinAddr ActualToRomAddress(TAny* anAddr)
+	{ return TLinAddr(anAddr)-TheRomMem+TheRomLinearAddress; }
+
+// class TVariantList
+TInt TVariantList::NumVariants;
+THardwareVariant TVariantList::Variants[TVariantList::EMaxVariants];
+void TVariantList::Setup(CObeyFile* aObey)
+	{
+	NumVariants=aObey->iNumberOfVariants;
+	if (NumVariants>EMaxVariants)
+		Print(EError,"Too many variants");
+	TInt i;
+	for(i=0; i<NumVariants; i++)
+		{
+		Variants[i]=aObey->iVariants[i]->iHardwareVariant;
+		}
+	}
+
+TVariantList::TVariantList(THardwareVariant a)
+	{
+	iList=0;
+	TInt i;
+	for (i=0; i<NumVariants; i++)
+		{
+		if (Variants[i]<=a)
+			iList|=TUint(1<<i);
+		}
+	}
+
+THardwareVariant TVariantList::Lookup() const
+	{
+	TInt i;
+	for (i=0; i<NumVariants; i++)
+		{
+		if (iList & TUint(1<<i))
+			return Variants[i];
+		}
+	return THardwareVariant(0);
+	}
+
+void TVariantList::SetNumVariants(TInt aNumVariants)
+{
+	NumVariants = aNumVariants;
+	if (NumVariants>EMaxVariants)
+		Print(EError,"Too many variants");
+}
+
+void TVariantList::SetVariants(THardwareVariant* aVariants)
+{
+	TInt Index = NumVariants;
+	while(Index--)
+	{
+		Variants[Index] = aVariants[Index];
+	}
+}
+
+void DumpRomEntry(const TRomEntry& e)
+	{
+	char name[256];
+	char* d = name;
+	const wchar_t* s = (const wchar_t*)e.iName;
+	const wchar_t* sE = s + e.iNameLength;
+	for (; s<sE; *d++ = (char)*s++) {}
+	*d++ = 0;
+	Print(ELog, "RomEntry @ %08x: SZ=%08x A=%08x att=%02x name %s\n", &e, e.iSize, e.iAddressLin, e.iAtt, name);
+	}
+
+// class Entry
+TRomEntry* Entry::CreateRomEntry(char*& anAddr) const
+	{
+
+	TRomEntry *pE=(TRomEntry*)anAddr;
+	pE->iAtt=iRomNode->iAtt;
+	pE->iSize=iRomNode->iRomFile->iAddresses.iSize;
+	pE->iAddressLin=iRomNode->iRomFile->iAddresses.iRunAddr;
+	if (IsFile())
+		iRomNode->iRomFile->SetRomEntry(pE);
+	pE->iName[0]=0;
+	pE->iName[1]=0;
+	int nameLen = strlen(iRomNode->iName);
+	if(Unicode){
+		UTF16String unistr(iRomNode->iName,nameLen);
+		pE->iNameLength = unistr.length();
+		memcpy(pE->iName,unistr.c_str(),unistr.bytes());
+		anAddr+=Align4(KRomEntrySize + unistr.bytes());
+	}
+	else{
+		memcpy(pE->iName,iRomNode->iName,nameLen);
+		anAddr+=Align4(KRomEntrySize + nameLen);
+		pE->iNameLength = nameLen ;
+	}
+ 
+	
+	TRACE(TDIR,DumpRomEntry(*pE));
+	return pE;
+	}
+
+const char* Entry::Name() const
+	{
+	return iRomNode->iName;
+	}
+
+// class FileEntry
+FileEntry::FileEntry(const FileEntry& aFileEntry)
+	: Entry(aFileEntry)
+	{
+	iVariants=aFileEntry.iVariants;
+	iRomNode=aFileEntry.iRomNode;
+	}
+
+FileEntry* FileEntry::New(TRomNode* aFile)
+	{
+	FileEntry* pE=new FileEntry();
+	if (pE)
+		{
+		pE->iRomNode=aFile;
+		pE->iVariants=TVariantList(aFile->HardwareVariant());
+		}
+	return pE;
+	}
+
+TInt FileEntry::Compare(const SetMember& aMember) const
+	{
+	TInt k=Type()-aMember.Type();
+	if (k!=0)
+		return k;
+	FileEntry *entry=(FileEntry *)&aMember;
+	return (iRomNode->iIdentifier-entry->iRomNode->iIdentifier);
+	}
+
+SetMember* FileEntry::Copy() const
+	{
+	return new FileEntry(*this);
+	}
+
+FileEntry::~FileEntry()
+	{
+	}
+
+// class DirEntry
+DirEntry::DirEntry(const DirEntry& aDirEntry)
+	: Entry(aDirEntry)
+	{
+	iVariants=aDirEntry.iVariants;
+	iRomNode=aDirEntry.iRomNode;
+	iDir=aDirEntry.iDir;
+	}
+
+DirEntry* DirEntry::New(TRomNode* aFile, Directory* aDir)
+	{
+	DirEntry* pE=new DirEntry();
+	if (pE)
+		{
+		pE->iRomNode=aFile;
+		pE->iVariants=aDir->iVariants;
+		pE->iDir=aDir;
+		if (aDir)
+			aDir->Open();
+		}
+	return pE;
+	}
+
+TInt DirEntry::Compare(const SetMember& aMember) const
+	{
+	TInt k=Type()-aMember.Type();
+	if (k!=0)
+		return k;
+	DirEntry *entry=(DirEntry *)&aMember;
+	return (iDir->iIdentifier - entry->iDir->iIdentifier);
+	}
+
+SetMember* DirEntry::Copy() const
+	{
+	DirEntry* pE=new DirEntry(*this);
+	if (pE && pE->iDir)
+		pE->iDir->Open();
+	return pE;
+	}
+
+DirEntry::~DirEntry()
+	{
+	if (iDir)
+		iDir->Close();
+	}
+
+// data structure and function for qsort
+struct SortableEntry 
+	{
+	unsigned int iOffset;
+	Entry* iEntry;
+	};
+
+int compare(const void* left, const void* right)
+	{
+	const SortableEntry* le  = (const SortableEntry*)left;
+	const SortableEntry* re = (const SortableEntry*)right;
+	if (le->iEntry->IsDir())
+		{
+		if (!re->iEntry->IsDir())
+			return -1;	// dir < file
+		}
+	else
+		{
+		if (re->iEntry->IsDir())
+			return +1;	// file > dir
+		}
+	// Both the same type of entry, sort by name.
+	// Sorting the 8-bit data using ASCII folding matches the sort order in terms of 16 bit
+	// characters provided that 8-bit data is actually CESU-8 rather than UTF-8. The two
+	// formats differ only when using surrogates (ie unicode values >= 0x10000). UTF-8 encodes
+	// an entire 32 bit value as a sequence of up to 6 bytes whereas CESU-8 encodes UTF-16
+	// values independently.
+	const char* l = (const char*)le->iEntry->Name();
+	const char* r = (const char*)re->iEntry->Name();
+	int result, lc, rc;
+	do	{
+		lc = *l++;
+		rc = *r++;
+		if (lc >= 'A' && lc <= 'Z')
+			lc += ('a' - 'A');
+		if (rc >= 'A' && rc <= 'Z')
+			rc += ('a' - 'A');
+		result = lc - rc;
+		} while (lc && result==0);
+	return result;
+	}
+
+
+TRomDir* DirEntry::CreateRomEntries(char*& anAddr) const
+	{
+	TInt i;
+	TInt count=iDir->Count();
+	TInt subdircount=0;
+	for(i=0; i<count; i++)
+		{
+		Entry* pE=(Entry*)&(*iDir)[i];
+		if (pE->IsDir())
+			{
+			subdircount++;
+			// Recursively build & place the subdirectories
+			DirEntry *pD=(DirEntry*)pE;
+			TRomDir *pR=pD->iDir->iRomDir;
+			if (!pR)
+				{
+				pR=pD->CreateRomEntries(anAddr);
+				pD->iDir->iRomDir=pR;
+				}
+			}
+		}
+	// Now place & build the TRomDir for this directory
+	TInt *pS=(TInt*)anAddr;
+	iDir->iRomDir=(TRomDir*)anAddr;
+	*pS=0;
+	anAddr+=sizeof(TInt);
+
+	char* offsetbase=anAddr;
+	SortableEntry* array=new SortableEntry [count];
+	if (array==0)
+		{
+		Print(EError,"Failed to allocate array of SortableEntry\n");
+		exit(-1);
+		}
+
+	for(i=0; i<count; i++)
+		{
+		Entry* pE=(Entry*)&(*iDir)[i];
+		array[i].iOffset=anAddr-offsetbase;
+		array[i].iEntry=pE;
+		TRomEntry *pR=pE->CreateRomEntry(anAddr);
+		if (pE->IsDir())
+			{
+			TRomDir *pD=((DirEntry*)pE)->iDir->iRomDir;
+			if (pD)
+				pR->iAddressLin=ActualToRomAddress(pD);
+			else
+				Print(EError,"Failed to fix up subdirectory address\n");
+			}
+		}
+	*pS=TInt(anAddr-(char*)pS-sizeof(TInt));
+
+	// Emit table of offsets for the subdirs and files in sorted order
+	if (gSortedRomFs)
+		{
+	TInt filecount=count-subdircount;
+	if (filecount>65535 || subdircount>65535)
+		{
+		Print(EError,"Too many files or subdirectories\n");
+		exit(-1);
+		}
+	TUint16* ptr=(TUint16*)anAddr;
+	*ptr++=(TUint16)subdircount;
+	*ptr++=(TUint16)filecount;
+	qsort(array,count,sizeof(SortableEntry),&compare);
+	for (i=0; i<count; i++)
+		{
+		unsigned int scaledOffset = array[i].iOffset>>2;
+		if ((array[i].iOffset & 3) != 0 || scaledOffset > 65535)
+			Print(EError, "Bad offset into directory\n");
+		*ptr++ = (TUint16)scaledOffset;
+		}
+	anAddr=(char*)ALIGN4((int)ptr);
+		}
+	delete [] array;
+	return (TRomDir*)pS;
+	}
+
+// class Directory
+TInt Directory::DirectoryCount=0;
+Directory::Directory(TInt aMaxCount)
+	: FiniteSet(aMaxCount), iRomDir(NULL), iAccessCount(1)
+	{
+	iIdentifier=Directory::DirectoryCount++;
+	}
+
+Directory* Directory::New(TInt aMaxCount, TVariantList aList)
+	{
+	Directory *pD=new Directory(aMaxCount);
+	if (pD)
+		{
+		pD->iVariants=aList;
+		pD=(Directory*)pD->Construct();
+		}
+	return pD;
+	}
+
+Directory::~Directory()
+	{
+	TRACE(TDIR,Print(EAlways,"Directory %08x Destruct\n",this));
+	}
+
+void Directory::Open()
+	{
+	iAccessCount++;
+	TRACE(TDIR,Print(EAlways,"Directory %08x Open() access count=%d\n",this,iAccessCount));
+	}
+
+void Directory::Close()
+	{
+	TRACE(TDIR,Print(EAlways,"Directory %08x Close() access count=%d\n",this,iAccessCount));
+	if (--iAccessCount==0)
+		delete this;
+	}
+
+TInt Directory::Compile(const FiniteSet& aSet)
+	{
+	TInt i;
+	TInt count=aSet.Count();
+	for(i=0; i<count; i++)
+		{
+		Entry *pE=(Entry*)&aSet[i];
+		if (iVariants<=pE->Variants())
+			{
+			Entry *pN=(Entry*)pE->Copy();
+			if (!pN)
+				return KErrNoMemory;
+			pN->Restrict(iVariants);
+			TInt r=Add(*pN);
+			if (r==KErrOverflow)
+				return r;
+			}
+		}
+	return KErrNone;
+	}
+
+TInt Directory::Merge(const Directory& aDir)
+	{
+	TInt i;
+	TInt r=Find(aDir,i);
+	if (r==KErrNone)
+		{
+		((Directory*)iMembers[i])->iVariants.Union(aDir.iVariants);
+		return KErrAlreadyExists;
+		}
+	else if (Insert(aDir,i)==KErrOverflow)
+		return KErrOverflow;
+	return KErrNone;
+	}
+
+// class RomFileStructure
+RomFileStructure::RomFileStructure(TInt aMaxCount)
+	: FiniteSet(aMaxCount)
+	{}
+
+RomFileStructure::~RomFileStructure()
+	{
+	}
+
+RomFileStructure* RomFileStructure::New(TInt aMaxCount)
+	{
+	RomFileStructure* pS=new RomFileStructure(aMaxCount);
+	if (pS)
+		pS=(RomFileStructure*)pS->Construct();
+	return pS;
+	}
+
+void RomFileStructure::Destroy()
+	{
+	}
+
+TInt RomFileStructure::ProcessDirectory(TRomNode* aDir)
+	{
+	TRACE(TSCRATCH, Print(EAlways, "ProcessDirectory (%08x) %s\n",aDir,aDir->iName));
+	TRACE(TDIR,Print(EAlways,"ProcessDirectory %s\nInitial:\n",aDir->iName));
+	TRACE(TDIR,DebugPrint());
+	TInt dirs=0;
+	TInt files=0;
+	aDir->CountDirectory(files,dirs);
+	TInt maxSize=files+dirs*TVariantList::NumVariants;
+	TRACE(TDIR,Print(EAlways,"files=%d dirs=%d maxSize=%d\n",files,dirs,maxSize));
+	RomFileStructure* pS=New(maxSize);
+	if (!pS)
+		return KErrNoMemory;
+	TInt r=aDir->ProcessDirectory(pS);
+	TRACE(TDIR,Print(EAlways,"FileList:\n"));
+	TRACE(TDIR,pS->DebugPrint());
+	Directory* dir[TVariantList::EMaxVariants];
+	TInt v;
+	for(v=0; v<TVariantList::NumVariants; v++)
+		{
+		TVariantList vList(v);
+		Directory *pD=Directory::New(files+dirs,vList);
+		if (!pD)
+			return KErrNoMemory;
+		dir[v]=pD;
+		r=pD->Compile(*pS);
+		if (r!=KErrNone)
+			return r;
+		TRACE(TDIR,Print(EAlways,"Variant %d Directory:\n",v));
+		TRACE(TDIR,pD->DebugPrint());
+		}
+	pS->Close();
+	Directory *pX=Directory::New(TVariantList::NumVariants,TVariantList());
+	if (!pX)
+		return KErrNoMemory;
+	for(v=0; v<TVariantList::NumVariants; v++)
+		{
+		if (dir[v]->Empty())
+			r=KErrAlreadyExists;
+		else
+			r=pX->Merge(*dir[v]);
+		if (r==KErrAlreadyExists)
+			{
+			dir[v]->Close();
+			dir[v]=NULL;
+			}
+		else if (r!=KErrNone)
+			return r;
+		}
+	TRACE(TDIR,Print(EAlways,"Final Directories:\n",v));
+	TRACE(TDIR,pX->DebugPrint());
+	TInt count=pX->Count();
+	TInt i;
+	for(i=0; i<count; i++)
+		{
+		Directory* pD=(Directory*)&(*pX)[i];
+		DirEntry* pE=DirEntry::New(aDir,pD);
+		if (!pE)
+			return KErrNoMemory;
+		r=Add(*pE);	// accumulate into the caller
+		if (r==KErrOverflow)
+			return r;
+		}
+	pX->Close();
+	return KErrNone;
+	}
+
+
+// DEBUG
+
+void FileEntry::DebugPrint() const
+	{
+	Print(EAlways,"FileEntry %08x %08x %s\n",iRomNode,iVariants.Mask(),iRomNode->iName);
+	}
+
+void DirEntry::DebugPrint() const
+	{
+	Print(EAlways,"DirEntry %08x %08x %08x %s\n",iRomNode,iVariants.Mask(),iDir,iRomNode->iName);
+	}
+
+void FiniteSet::DebugPrint() const
+	{
+	if (Count()==0)
+		Print(EAlways,"FiniteSet 0\n");
+	else
+		{
+		Print(EAlways,"FiniteSet %d {\n",Count());
+		TInt i;
+		for (i=0; i<Count(); i++)
+			{
+			iMembers[i]->DebugPrint();
+			}
+		Print(EAlways,"}\n");
+		}
+	}
+
+void Directory::DebugPrint() const
+	{
+	Print(EAlways,"Directory %08x %08x\n",iVariants.Mask(),iRomDir);
+	FiniteSet::DebugPrint();
+	}
+
+
+
+
+