/*
* Copyright 1995, 1996 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
//
// mapping.cc - the mapping master
//
# include <stdhdrs.h>
# include <error.h>
# include <strbuf.h>
# include <vararray.h>
# include <debug.h>
# include <tunable.h>
# include <msgdb.h>
# include "maphalf.h"
# include "maptable.h"
# include "mapstring.h"
# include "mapdebug.h"
# include "mapitem.h"
/*
* mapFlagGrid -- how to combine two mapFlags
*/
MapFlag mapFlagGrid[6][6] = {
/* Map */ /* Unmap */ /* Remap */ /* Havemap */ /* Changemap */ /* Andmap */
/* Map */ MfMap, MfUnmap, MfRemap, MfHavemap, MfChangemap, MfAndmap,
/* Unmap */ MfUnmap, MfUnmap, MfUnmap, MfUnmap, MfUnmap, MfUnmap,
/* Remap */ MfRemap, MfUnmap, MfRemap, MfHavemap, MfChangemap, MfAndmap,
/* Havemap */ MfHavemap, MfUnmap, MfHavemap, MfHavemap, MfHavemap, MfAndmap,
/* Changemap */ MfChangemap, MfUnmap, MfChangemap, MfHavemap, MfChangemap, MfAndmap,
/* Andmap */ MfAndmap, MfUnmap, MfAndmap, MfAndmap, MfChangemap, MfAndmap,
} ;
class MapJoiner : public Joiner {
public:
MapJoiner()
{
badJoin = 0;
m0 = new MapTable;
}
virtual void Insert()
{
map->Lhs()->Expand( *this, newLhs, params );
map->Rhs()->Expand( *this, newRhs, params );
MapFlag mapFlag = mapFlagGrid[ map->Flag() ][ map2->Flag() ];
m0->InsertNoDups( newLhs, newRhs, mapFlag );
}
public:
MapTable *m0;
MapItem *map;
MapItem *map2;
protected:
StrBuf newLhs;
StrBuf newRhs;
} ;
class MapJoiner2 : public MapJoiner {
public:
MapTableT dir1;
MapTableT dir2;
MapJoiner2( MapTableT dir1, MapTableT dir2 )
{
this->dir1 = dir1;
this->dir2 = dir2;
}
virtual void Insert()
{
map->Ohs( dir1 )->Expand( *this, newLhs, params );
map2->Ohs( dir2 )->Expand( *this, newRhs, params2 );
MapFlag mapFlag = mapFlagGrid[ map->Flag() ][ map2->Flag() ];
m0->InsertNoDups( newLhs, newRhs, mapFlag );
}
} ;
void
MapTable::JoinOptimizer( MapTableT dir2 )
{
if( !trees[ dir2 ].tree )
MakeTree( dir2 );
}
void
MapTable::Join(
MapTable *m1, MapTableT dir1,
MapTable *m2, MapTableT dir2,
MapJoiner *j, const ErrorId *reason )
{
if( DEBUG_JOIN )
{
m1->Dump( dir1 == LHS ? "lhs" : "rhs" );
m2->Dump( dir2 == LHS ? "lhs" : "rhs" );
}
// Give up if we internally produce more than 1,000,000 rows
// or more than 10000 + the sum of the two mappings.
// This can happen when joining multiple lines with multiple
// ...'s on them.
const int max1 = p4tunable.Get( P4TUNE_MAP_JOINMAX1 );
const int max2 = p4tunable.Get( P4TUNE_MAP_JOINMAX2 );
int m = max1 + m1->count + m2->count;
if( m > max2 ) m = max2;
if( !m2->trees[ dir2 ].tree )
{
for(j->map = m1->entry; j->map && count<m; j->map = j->map->Next())
for(j->map2 = m2->entry; j->map2; j->map2 = j->map2->Next())
{
j->map->Ths( dir1 )->Join( j->map2->Ths( dir2 ), *j );
if( j->badJoin )
{
joinError = 1;
emptyReason = &MsgDb::TooWild2;
reason = &MsgDb::TooWild2;
this->joinError = 1;
this->emptyReason = &MsgDb::TooWild;
return;
}
}
}
else
{
// Generate a list of possible pairs of maps to join.
// These are maps whose non-wild initial substrings match.
// We used to just do for()for(), but when joining large
// maps that's slow. So the inner loop is now a tree
// search.
MapPairArray pairArray( dir1, dir2 );
if( m2->trees[ dir2 ].tree )
for( MapItem *i1 = m1->entry; i1 && count < m; i1 = i1->Next() )
{
// This inserts entries in tree order,
// rather than chain order (precedence), so we have to
// resort before doing the MapHalf::Join() calls.
pairArray.Clear();
pairArray.Match( i1, m2->trees[ dir2 ].tree );
pairArray.Sort();
MapPair *jp;
// Walk the list of possible pairs and call MapHalf::Join()
// to do the wildcard joining.
for( int i = 0; jp = pairArray.Get(i); i++ )
{
j->map = jp->item1;
j->map2 = jp->tree2;
jp->h1->Join( jp->h2, *j );
delete jp;
}
}
}
// Insert() reverses the order
this->Reverse();
// Empty reasons
if( count >= m )
{
emptyReason = &MsgDb::TooWild;
Clear();
}
else if( m1->IsEmpty() && m1->emptyReason )
{
emptyReason = m1->emptyReason;
}
else if( m2->IsEmpty() && m2->emptyReason )
{
emptyReason = m2->emptyReason;
}
else if( !hasMaps && reason )
{
emptyReason = reason;
}
if( DEBUG_JOIN )
this->Dump( "map joined" );
}
MapTable *
MapTable::Join(
MapTableT dir1,
MapTable *m2,
MapTableT dir2,
const ErrorId *reason )
{
MapJoiner j;
j.m0->Join( this, dir1, m2, dir2, &j, reason );
return j.m0;
}
MapTable *
MapTable::Join2(
MapTableT dir1,
MapTable *m2,
MapTableT dir2,
const ErrorId *reason )
{
MapJoiner2 j( dir1, dir2 );
j.m0->Join( this, dir1, m2, dir2, &j, reason );
return j.m0;
}
class MapDisambiguate : public MapJoiner {
public:
virtual void Insert()
{
map->Lhs()->Expand( *this, newLhs, params2 );
map->Rhs()->Expand( *this, newRhs, params2 );
m0->InsertNoDups( newLhs, newRhs, MfUnmap );
}
} ;
/*
* MapTable::JoinCheck() - does this maptable include this string?
*
* This isn't simply finding if a file is mapped by the table, but
* rather if a _mapping_ is mapped by the table. So we have to take
* the mapping, put it into its own table, join it, and then throw
* it all away. Kinda _slow_.
*/
int
MapTable::JoinCheck( MapTableT dir, const StrPtr &lhs )
{
MapTable c;
c.Insert( lhs );
MapTable *j = c.Join( LHS, this, dir );
int empty = j->IsEmpty();
delete j;
return !empty;
}
/*
* MapTable::Disambiguate() - handle un/ambiguous mappings
*
* Mappings provided by the user (client views, branch views)
* can be ambiguous since later mappings can override earlier
* ones. Disambiguate() adds explicit exclusion (-) mappings
* for any ambiguous mappings, so that Join() and Translate()
* don't have to worry about them.
*
* For example:
*
* a/... b/...
* a/c b/d
*
* becomes
*
* a/... b/...
* -a/c -b/c
* -a/d -b/d
* a/c b/d
*
* Unmaps are handled similarly, but the high precedence unmap
* line itself is not added, because everything that can be unmapped
* has been done so by joining it against the lower precedence
* mapping lines:
*
* a/... b/...
* -a/c b/d
*
* becomes
*
* a/... b/...
* -a/c -b/c
* -a/d -b/d
*/
void
MapTable::Disambiguate()
{
MapDisambiguate j;
// From high precendence to low precedence
for( j.map = this->entry; j.map; j.map = j.map->Next() )
{
// We skip unmap lines, because we only need to
// unmap to the extent that the unmap lines match lower
// precedence map lines. We do that below.
switch( j.map->Flag() )
{
case MfUnmap:
continue;
}
// Look for higher precedence mappings that match (join)
// this mapping, and to the extent that they overlap add
// unmappings. We do this for both MfMap and MfUnmap.
// A higher precedence MfRemap doesn't occlude us, so
// we skip those.
// From higher precedence back down to this mapping
for( j.map2 = this->entry;
j.map2 != j.map;
j.map2 = j.map2->Next() )
{
switch( j.map2->Flag() )
{
case MfRemap:
case MfHavemap:
break;
case MfAndmap:
j.map2->Lhs()->Join( j.map2->Rhs(), j );
j.map2->Rhs()->Join( j.map->Rhs(), j );
break;
default:
j.map2->Lhs()->Join( j.map->Lhs(), j );
j.map2->Rhs()->Join( j.map->Rhs(), j );
}
}
// now the original map entry
j.m0->Insert( *j.map->Lhs(), *j.map->Rhs(), j.map->Flag() );
}
// Inserted order leaves low precedence at the head of the
// list. Reverse to get it back where it belongs.
j.m0->Reverse();
// Just zonk our own and copy j.m0's
Clear();
Insert( j.m0, 1, 0 );
delete j.m0;
}