1 /***************************************************************************
2  *   (C) 2005-2006 Marius Roets <roets.marius@gmail.com>                   *
3  *   (C) 2007 Rico Zenklusen <rico_z@users.sourceforge.net>                *
4  *   (C) 2007-2009 Michal Rudolf <mrudolf@kdewebdev.org>                   *
5  *                                                                         *
6  *   This program is free software; you can redistribute it and/or modify  *
7  *   it under the terms of the GNU General Public License as published by  *
8  *   the Free Software Foundation; either version 2 of the License, or     *
9  *   (at your option) any later version.                                   *
10  ***************************************************************************/
11 
12 #include <QtDebug>
13 #include <QFile>
14 #include <QDataStream>
15 #include <QHash>
16 #include <QMultiHash>
17 #include <QWriteLocker>
18 #include <QReadLocker>
19 #include <QVector>
20 
21 #include "index.h"
22 #include "tags.h"
23 
24 using namespace chessx;
25 
26 #if defined(_MSC_VER) && defined(_DEBUG)
27 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
28 #define new DEBUG_NEW
29 #endif // _MSC_VER
30 
IndexX()31 IndexX::IndexX() : m_mutex(QReadWriteLock::Recursive)
32 {
33     // Dummy Values in case a index is miscalculated
34     init();
35 }
36 
~IndexX()37 IndexX::~IndexX()
38 {
39 }
40 
add()41 GameId IndexX::add()
42 {
43     QWriteLocker m(&m_mutex);
44     GameId gameId = m_indexItems.count();
45     m_indexItems.insert(gameId, IndexItem());
46     return gameId;
47 }
48 
AddTagName(QString name)49 TagIndex IndexX::AddTagName(QString name)
50 {
51     if(m_tagNameIndex.contains(name))
52     {
53         return m_tagNameIndex.value(name);
54     }
55     TagIndex n = m_tagNameIndex.size();
56     m_tagNameIndex[name] = n;
57     m_tagNames[n] = name;
58     return n;
59 }
60 
AddTagValue(QString name)61 ValueIndex IndexX::AddTagValue(QString name)
62 {
63     ValueIndex n = qHash(name);
64     if (m_tagValues.contains(n))
65     {
66         if (m_tagValues[n] == name)
67         {
68             return n;
69         }
70         name.append(QChar(0));
71         QString prelim = name;
72         int i = 0;
73         do {
74             prelim = name + QString::number(i++);
75             n = qHash(prelim);
76             if (m_tagValues.contains(n))
77             {
78                 if (m_tagValues[n] == prelim)
79                 {
80                     return n;
81                 }
82             }
83         } while(m_tagValues.contains(n));
84         name = prelim;
85     }
86     m_tagValues[n] = name;
87     return n;
88 }
89 
setTag(const QString & tagName,const QString & value,GameId gameId)90 void IndexX::setTag(const QString& tagName, const QString& value, GameId gameId)
91 {
92 	QWriteLocker m(&m_mutex); // PERF 10s aus 30s (aus 115s Gesamtdatei)
93 	setTag_nolock(tagName, value, gameId);
94 }
95 
setTag_nolock(const QString & tagName,const QString & value,GameId gameId)96 void IndexX::setTag_nolock(const QString& tagName, const QString& value, GameId gameId)
97 {
98 	TagIndex tagIndex = AddTagName(tagName);
99 	ValueIndex valueIndex = AddTagValue(value);
100 
101 	if (m_indexItems.count() <= (int)gameId)
102 	{
103 		(void) add();
104 	}
105 	m_indexItems[gameId].set(tagIndex, valueIndex);
106 }
107 
removeTag(const QString & tagName,GameId gameId)108 void IndexX::removeTag(const QString& tagName, GameId gameId)
109 {
110     QWriteLocker m(&m_mutex);
111     if(m_tagNameIndex.contains(tagName))
112     {
113         TagIndex tagIndex = m_tagNameIndex.value(tagName);
114         if((int)gameId < m_indexItems.count())
115         {
116             m_indexItems[gameId].remove(tagIndex);
117         }
118     }
119 }
120 
setValidFlag(GameId gameId,bool value)121 void IndexX::setValidFlag(GameId gameId, bool value)
122 {
123     if (!value)
124     {
125         m_validFlags.insert(gameId);
126     }
127     else
128     {
129         m_validFlags.remove(gameId);
130     }
131 }
132 
replaceTagValue(QStringList tags,const QString & newValue,const QString & oldValue)133 bool IndexX::replaceTagValue(QStringList tags, const QString& newValue, const QString& oldValue)
134 {
135     QWriteLocker m(&m_mutex);
136 
137     bool ok = false;
138     foreach(QString t, tags)
139     {
140         if(m_tagNameIndex.contains(t))
141         {
142             ok = true;
143             break;
144         }
145     }
146     ValueIndex valueIndex = getValueIndex(oldValue);
147     if(!ok || !m_tagValues.contains(valueIndex))
148     {
149         return false;
150     }
151     ValueIndex newIndex = getValueIndex(newValue);
152 
153     if (!m_tagValues.contains(newIndex))
154     {
155         (void) AddTagValue(newValue); // Adds newIndex-newValue pair, return must be newIndex
156     }
157 
158     QList<TagIndex> tl;
159     foreach (QString t, tags)
160     {
161         tl << getTagIndex(t);
162     }
163 
164     QVector<IndexItem>::iterator i;
165     for (i = m_indexItems.begin(); i != m_indexItems.end(); ++i)
166     {
167         i->replaceValue(tl, valueIndex, newIndex);
168     }
169 
170     m_tagValues.remove(valueIndex);
171     return true;
172 }
173 
isValidFlag(GameId gameId) const174 bool IndexX::isValidFlag(GameId gameId) const
175 {
176     QReadLocker m(&m_mutex);
177     return !m_validFlags.contains(gameId);
178 }
179 
write(QDataStream & out) const180 bool IndexX::write(QDataStream &out) const
181 {
182     QReadLocker m(&m_mutex);
183 
184     out << m_tagNames;
185     out << m_tagValues;
186 	out << m_indexItems;
187     out << m_validFlags;
188 
189     bool extension = false;
190     out << extension;
191 
192     return true;
193 }
194 
reserve(quint32 estimation)195 void IndexX::reserve(quint32 estimation)
196 {
197     m_tagValues.reserve(estimation+16);
198     qDebug() << "Index space " << m_tagValues.capacity();
199 }
200 
squeeze()201 void IndexX::squeeze()
202 {
203     qDebug() << "Index space " << m_tagValues.capacity();
204     m_tagValues.squeeze();
205     qDebug() << "Index space " << m_tagValues.capacity();
206 }
207 
read(QDataStream & in,volatile bool * breakFlag,short version)208 bool IndexX::read(QDataStream &in, volatile bool *breakFlag, short version)
209 {
210     Q_UNUSED(version);
211 
212     QWriteLocker m(&m_mutex);
213 
214     in >> m_tagNames;
215     in >> m_tagValues;
216 	in >> m_indexItems;
217     in >> m_validFlags;
218 
219 	bool extension;
220     in >> extension;
221 
222     m_tagNameIndex.clear();
223 
224     calculateCache(breakFlag);
225 
226     return !(*breakFlag);
227 }
228 
clearCache()229 void IndexX::clearCache()
230 {
231     QWriteLocker m(&m_mutex);
232     m_tagNameIndex.clear();
233 }
234 
calculateCache(volatile bool * breakFlag)235 void IndexX::calculateCache(volatile bool* breakFlag)
236 {
237     calculateReverseMaps(breakFlag);
238 }
239 
calculateReverseMaps(volatile bool * breakFlag)240 void IndexX::calculateReverseMaps(volatile bool* breakFlag)
241 {
242     if(m_tagNameIndex.isEmpty())
243     {
244         for (auto it = m_tagNames.cbegin(); it != m_tagNames.cend(); ++it)
245         {
246             if(breakFlag && *breakFlag)
247             {
248                 return;
249             }
250             m_tagNameIndex.insert(it.value(), it.key());
251         }
252     }
253 }
254 
init()255 void IndexX::init()
256 {
257     AddTagName("?");
258     AddTagValue("?");
259 }
260 
clear()261 void IndexX::clear()
262 {
263     QWriteLocker m(&m_mutex);
264     m_indexItems.clear();
265     m_tagNames.clear();
266     m_tagNameIndex.clear();
267     m_tagValues.clear();
268     m_deletedGames.clear();
269     m_validFlags.clear();
270     init(); // Just to make sure that the index can be used after clearing
271 }
272 
count() const273 int IndexX::count() const
274 {
275     return m_indexItems.count();
276 }
277 
listInSet(const QString & tagName,const QSet<QString> & set) const278 QBitArray IndexX::listInSet(const QString& tagName, const QSet<QString>& set) const
279 {
280     QReadLocker m(&m_mutex);
281 
282     TagIndex tagIndex = m_tagNameIndex.value(tagName);
283 
284     QBitArray list(count(), false);
285     for(int i = 0; i < count(); ++i)
286     {
287         QString value = tagValue(tagIndex, i);
288         bool b = false;
289         foreach(QString s, set)
290         {
291             if (value.contains(s, Qt::CaseInsensitive))
292             {
293                 b = true;
294                 break;
295             }
296         }
297         list.setBit(i, b);
298     }
299     return list;
300 }
301 
listInRange(const QString & tagName,const QString & minValue,const QString & maxValue) const302 QBitArray IndexX::listInRange(const QString& tagName, const QString& minValue, const QString& maxValue) const
303 {
304     QReadLocker m(&m_mutex);
305 
306     TagIndex tagIndex = m_tagNameIndex.value(tagName);
307 
308     QBitArray list(count(), false);
309     for(int i = 0; i < count(); ++i)
310     {
311         QString value = tagValue(tagIndex, i);
312         list.setBit(i, (minValue <= value) && (value <= maxValue));
313     }
314     return list;
315 }
316 
listInRange(const QString & tagName,int minValue,int maxValue) const317 QBitArray IndexX::listInRange(const QString &tagName, int minValue, int maxValue) const
318 {
319     QReadLocker m(&m_mutex);
320 
321     TagIndex tagIndex = m_tagNameIndex.value(tagName);
322 
323     QBitArray list(count(), false);
324     for(int i = 0; i < count(); ++i)
325     {
326         int value = tagValue(tagIndex, i).toInt();
327         list.setBit(i, (minValue <= value) && (value <= maxValue));
328     }
329     return list;
330 }
331 
listPartialValue(const QString & tagName,QString value) const332 QBitArray IndexX::listPartialValue(const QString& tagName, QString value) const
333 {
334     QReadLocker m(&m_mutex);
335     value.replace("-","\\-"); // Avoid - to become range
336     value.replace("(","\\("); // Avoid () to become regex
337     value.replace(")","\\)");
338     TagIndex tagIndex = m_tagNameIndex.value(tagName);
339     QRegExp re(value);
340     re.setCaseSensitivity(Qt::CaseInsensitive);
341     QBitArray list(count(), false);
342     for(int i = 0; i < count(); ++i)
343     {
344         QString gameValue = tagValue(tagIndex, i);
345         list.setBit(i, gameValue.contains(re));
346     }
347     return list;
348 }
349 
tagValue_byIndex(TagIndex tagIndex,GameId gameId) const350 QString IndexX::tagValue_byIndex(TagIndex tagIndex, GameId gameId) const
351 {
352     QReadLocker m(&m_mutex);
353 
354     ValueIndex valueIndex = m_indexItems[gameId].valueIndex(tagIndex);
355 
356     return tagValueName(valueIndex);
357 }
358 
tagValue(TagIndex tagIndex,GameId gameId) const359 QString IndexX::tagValue(TagIndex tagIndex, GameId gameId) const
360 {
361     ValueIndex valueIndex = m_indexItems[gameId].valueIndex(tagIndex);
362 
363     return tagValueName(valueIndex);
364 }
365 
tagName(TagIndex tagIndex) const366 QString IndexX::tagName(TagIndex tagIndex) const
367 {
368     return m_tagNames.value(tagIndex);
369 }
370 
tagValueName(ValueIndex valueIndex) const371 QString IndexX::tagValueName(ValueIndex valueIndex) const
372 {
373     QString r = m_tagValues.value(valueIndex);
374     return r.section(QChar(0),0,0);
375 }
376 
tagValue(const QString & tagName,GameId gameId) const377 QString IndexX::tagValue(const QString& tagName, GameId gameId) const
378 {
379     QReadLocker m(&m_mutex);
380     TagIndex tagIndex = m_tagNameIndex.value(tagName);
381     return tagValue(tagIndex, gameId);
382 }
383 
valueIndexFromTag(const QString & tagName,GameId gameId) const384 ValueIndex IndexX::valueIndexFromTag(const QString& tagName, GameId gameId) const
385 {
386     QReadLocker m(&m_mutex);
387     TagIndex tagIndex = m_tagNameIndex.value(tagName);
388     return valueIndexFromIndex(tagIndex, gameId);
389 }
390 
indexItemHasTag(TagIndex tagIndex,GameId gameId) const391 bool IndexX::indexItemHasTag(TagIndex tagIndex, GameId gameId) const
392 {
393     return m_indexItems[gameId].hasTagIndex(tagIndex);
394 }
395 
valueIndexFromIndex(TagIndex tagIndex,GameId gameId) const396 inline ValueIndex IndexX::valueIndexFromIndex(TagIndex tagIndex, GameId gameId) const
397 {
398     return m_indexItems[gameId].valueIndex(tagIndex);
399 }
400 
getTagIndex(const QString & value) const401 TagIndex IndexX::getTagIndex(const QString& value) const
402 {
403     if(m_tagNameIndex.contains(value))
404     {
405         return m_tagNameIndex.value(value);
406     }
407     return TagNoIndex;
408 }
409 
getValueIndex(QString name) const410 ValueIndex IndexX::getValueIndex(QString name) const
411 {
412     ValueIndex n = qHash(name);
413 
414     if (m_tagValues.contains(n))
415     {
416         if (m_tagValues[n] == name)
417         {
418             return n;
419         }
420         name.append(QChar(0));
421         QString prelim;
422         int i = 0;
423         do {
424             prelim = name + QString::number(i++);
425             n = qHash(prelim);
426             if (m_tagValues.contains(n))
427             {
428                 if (m_tagValues[n] == prelim)
429                 {
430                     return n;
431                 }
432             }
433         } while(m_tagValues.contains(n));
434     }
435 
436     return n;
437 }
438 
hashIndexItem(GameId gameId) const439 unsigned int IndexX::hashIndexItem(GameId gameId) const
440 {
441     return valueIndexFromTag(TagNameWhite, gameId);
442 }
443 
isIndexItemEqual(GameId i,GameId j) const444 bool IndexX::isIndexItemEqual(GameId i, GameId j) const
445 {
446     QReadLocker m(&m_mutex);
447     const IndexItem& iItem = m_indexItems.value(i);
448     const IndexItem& jItem = m_indexItems.value(j);
449     return (iItem.isEqual(jItem));
450 }
451 
loadGameHeaders(GameId id,GameX & game) const452 void IndexX::loadGameHeaders(GameId id, GameX& game) const
453 {
454     QReadLocker m(&m_mutex);
455 
456     game.clearTags();
457     foreach(TagIndex tagIndex, m_indexItems[id].getTagIndices())
458     {
459         // qDebug() << "lGH>" << &game << " " << id << " " << tagName(tagIndex) << " " << tagValue(tagIndex, id);
460         game.setTag(tagName(tagIndex), tagValue(tagIndex, id));
461     }
462 }
463 
loadGameHeader(GameId id,GameX & game,const QString & tag) const464 void IndexX::loadGameHeader(GameId id, GameX& game, const QString &tag) const
465 {
466     QReadLocker m(&m_mutex);
467 
468     TagIndex tagIndex = getTagIndex(tag);
469     game.setTag(tagName(tagIndex), tagValue(tagIndex, id));
470 }
471 
playerNames() const472 QStringList IndexX::playerNames() const
473 {
474     QReadLocker m(&m_mutex);
475 
476     QStringList allPlayerNames;
477     QSet<ValueIndex> playerNameIndex;
478 
479     TagIndex tagIndex = getTagIndex(TagNameWhite);
480     if(tagIndex != TagNoIndex)
481     {
482         QVector<IndexItem>::const_iterator i;
483 		for (i = m_indexItems.constBegin(); i != m_indexItems.constEnd(); ++i)
484 		{
485             playerNameIndex.insert(i->valueIndex(tagIndex));
486 		}
487     }
488 
489     tagIndex = getTagIndex(TagNameBlack);
490     if(tagIndex != TagNoIndex)
491     {
492         QVector<IndexItem>::const_iterator i;
493 		for (i = m_indexItems.constBegin(); i != m_indexItems.constEnd(); ++i)
494 		{
495             playerNameIndex.insert(i->valueIndex(tagIndex));
496 		}
497 	}
498 
499     foreach(ValueIndex valueIndex, playerNameIndex)
500     {
501         allPlayerNames.append(tagValueName(valueIndex));
502     }
503 
504     return allPlayerNames;
505 }
506 
tagValueSet(const QString & tagName) const507 QSet<ValueIndex> IndexX::tagValueSet(const QString& tagName) const
508 {
509 	QReadLocker m(&m_mutex);
510 
511 	QSet<ValueIndex> tagNameIndex;
512 	TagIndex tagIndex = getTagIndex(tagName);
513 
514 	if (tagIndex != TagNoIndex)
515 	{
516         QVector<IndexItem>::const_iterator i;
517 		for (i = m_indexItems.constBegin(); i != m_indexItems.constEnd(); ++i)
518 		{
519             tagNameIndex.insert(i->valueIndex(tagIndex));
520 		}
521 	}
522 	return tagNameIndex;
523 }
524 
tagValues(const QString & tagName) const525 QStringList IndexX::tagValues(const QString& tagName) const
526 {
527 	QStringList allTagNames;
528 	QSet<ValueIndex> set = tagValueSet(tagName);
529     foreach(ValueIndex valueIndex, set)
530     {
531         allTagNames.append(tagValueName(valueIndex));
532     }
533     return allTagNames;
534 }
535 
deleted(GameId gameId) const536 bool IndexX::deleted(GameId gameId) const
537 {
538     return m_deletedGames.contains(gameId);
539 }
540 
setDeleted(GameId gameId,bool df)541 void IndexX::setDeleted(GameId gameId, bool df)
542 {
543     if (df)
544     {
545         m_deletedGames.insert(gameId);
546     }
547     else
548     {
549         m_deletedGames.remove(gameId);
550     }
551 }
552