list.cpp

00001 /*****************************************************************************
00002 Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois.
00003 All rights reserved.
00004 
00005 Redistribution and use in source and binary forms, with or without
00006 modification, are permitted provided that the following conditions are
00007 met:
00008 
00009 * Redistributions of source code must retain the above
00010   copyright notice, this list of conditions and the
00011   following disclaimer.
00012 
00013 * Redistributions in binary form must reproduce the
00014   above copyright notice, this list of conditions
00015   and the following disclaimer in the documentation
00016   and/or other materials provided with the distribution.
00017 
00018 * Neither the name of the University of Illinois
00019   nor the names of its contributors may be used to
00020   endorse or promote products derived from this
00021   software without specific prior written permission.
00022 
00023 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
00024 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
00025 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
00026 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
00027 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
00028 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00029 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00030 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00031 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00032 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00033 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00034 *****************************************************************************/
00035 
00036 /*****************************************************************************
00037 written by
00038    Yunhong Gu, last updated 01/22/2011
00039 *****************************************************************************/
00040 
00041 #include "list.h"
00042 
00043 CSndLossList::CSndLossList(int size):
00044 m_piData1(NULL),
00045 m_piData2(NULL),
00046 m_piNext(NULL),
00047 m_iHead(-1),
00048 m_iLength(0),
00049 m_iSize(size),
00050 m_iLastInsertPos(-1),
00051 m_ListLock()
00052 {
00053    m_piData1 = new int32_t [m_iSize];
00054    m_piData2 = new int32_t [m_iSize];
00055    m_piNext = new int [m_iSize];
00056 
00057    // -1 means there is no data in the node
00058    for (int i = 0; i < size; ++ i)
00059    {
00060       m_piData1[i] = -1;
00061       m_piData2[i] = -1;
00062    }
00063 
00064    // sender list needs mutex protection
00065    #ifndef WIN32
00066       pthread_mutex_init(&m_ListLock, 0);
00067    #else
00068       m_ListLock = CreateMutex(NULL, false, NULL);
00069    #endif
00070 }
00071 
00072 CSndLossList::~CSndLossList()
00073 {
00074    delete [] m_piData1;
00075    delete [] m_piData2;
00076    delete [] m_piNext;
00077 
00078    #ifndef WIN32
00079       pthread_mutex_destroy(&m_ListLock);
00080    #else
00081       CloseHandle(m_ListLock);
00082    #endif
00083 }
00084 
00085 int CSndLossList::insert(int32_t seqno1, int32_t seqno2)
00086 {
00087    CGuard listguard(m_ListLock);
00088 
00089    if (0 == m_iLength)
00090    {
00091       // insert data into an empty list
00092 
00093       m_iHead = 0;
00094       m_piData1[m_iHead] = seqno1;
00095       if (seqno2 != seqno1)
00096          m_piData2[m_iHead] = seqno2;
00097 
00098       m_piNext[m_iHead] = -1;
00099       m_iLastInsertPos = m_iHead;
00100 
00101       m_iLength += CSeqNo::seqlen(seqno1, seqno2);
00102 
00103       return m_iLength;
00104    }
00105 
00106    // otherwise find the position where the data can be inserted
00107    int origlen = m_iLength;
00108    int offset = CSeqNo::seqoff(m_piData1[m_iHead], seqno1);
00109    int loc = (m_iHead + offset + m_iSize) % m_iSize;
00110 
00111    if (offset < 0)
00112    {
00113       // Insert data prior to the head pointer
00114 
00115       m_piData1[loc] = seqno1;
00116       if (seqno2 != seqno1)
00117          m_piData2[loc] = seqno2;
00118 
00119       // new node becomes head
00120       m_piNext[loc] = m_iHead;
00121       m_iHead = loc;
00122       m_iLastInsertPos = loc;
00123 
00124       m_iLength += CSeqNo::seqlen(seqno1, seqno2);
00125    }
00126    else if (offset > 0)
00127    {
00128       if (seqno1 == m_piData1[loc])
00129       {
00130          m_iLastInsertPos = loc;
00131 
00132          // first seqno is equivlent, compare the second
00133          if (-1 == m_piData2[loc])
00134          {
00135             if (seqno2 != seqno1)
00136             {
00137                m_iLength += CSeqNo::seqlen(seqno1, seqno2) - 1;
00138                m_piData2[loc] = seqno2;
00139             }
00140          }
00141          else if (CSeqNo::seqcmp(seqno2, m_piData2[loc]) > 0)
00142          {
00143             // new seq pair is longer than old pair, e.g., insert [3, 7] to [3, 5], becomes [3, 7]
00144             m_iLength += CSeqNo::seqlen(m_piData2[loc], seqno2) - 1;
00145             m_piData2[loc] = seqno2;
00146          }
00147          else
00148             // Do nothing if it is already there
00149             return 0;
00150       }
00151       else
00152       {
00153          // searching the prior node
00154          int i;
00155          if ((-1 != m_iLastInsertPos) && (CSeqNo::seqcmp(m_piData1[m_iLastInsertPos], seqno1) < 0))
00156             i = m_iLastInsertPos;
00157          else
00158             i = m_iHead;
00159 
00160          while ((-1 != m_piNext[i]) && (CSeqNo::seqcmp(m_piData1[m_piNext[i]], seqno1) < 0))
00161             i = m_piNext[i];
00162 
00163          if ((-1 == m_piData2[i]) || (CSeqNo::seqcmp(m_piData2[i], seqno1) < 0))
00164          {
00165             m_iLastInsertPos = loc;
00166 
00167             // no overlap, create new node
00168             m_piData1[loc] = seqno1;
00169             if (seqno2 != seqno1)
00170                m_piData2[loc] = seqno2;
00171 
00172             m_piNext[loc] = m_piNext[i];
00173             m_piNext[i] = loc;
00174 
00175             m_iLength += CSeqNo::seqlen(seqno1, seqno2);
00176          }
00177          else
00178          {
00179             m_iLastInsertPos = i;
00180 
00181             // overlap, coalesce with prior node, insert(3, 7) to [2, 5], ... becomes [2, 7]
00182             if (CSeqNo::seqcmp(m_piData2[i], seqno2) < 0)
00183             {
00184                m_iLength += CSeqNo::seqlen(m_piData2[i], seqno2) - 1;
00185                m_piData2[i] = seqno2;
00186 
00187                loc = i;
00188             }
00189             else
00190                return 0;
00191          }
00192       }
00193    }
00194    else
00195    {
00196       m_iLastInsertPos = m_iHead;
00197 
00198       // insert to head node
00199       if (seqno2 != seqno1)
00200       {
00201          if (-1 == m_piData2[loc])
00202          {
00203             m_iLength += CSeqNo::seqlen(seqno1, seqno2) - 1;
00204             m_piData2[loc] = seqno2;
00205          }
00206          else if (CSeqNo::seqcmp(seqno2, m_piData2[loc]) > 0)
00207          {
00208             m_iLength += CSeqNo::seqlen(m_piData2[loc], seqno2) - 1;
00209             m_piData2[loc] = seqno2;
00210          }
00211          else 
00212             return 0;
00213       }
00214       else
00215          return 0;
00216    }
00217 
00218    // coalesce with next node. E.g., [3, 7], ..., [6, 9] becomes [3, 9] 
00219    while ((-1 != m_piNext[loc]) && (-1 != m_piData2[loc]))
00220    {
00221       int i = m_piNext[loc];
00222 
00223       if (CSeqNo::seqcmp(m_piData1[i], CSeqNo::incseq(m_piData2[loc])) <= 0)
00224       {
00225          // coalesce if there is overlap
00226          if (-1 != m_piData2[i])
00227          {
00228             if (CSeqNo::seqcmp(m_piData2[i], m_piData2[loc]) > 0)
00229             {
00230                if (CSeqNo::seqcmp(m_piData2[loc], m_piData1[i]) >= 0)
00231                   m_iLength -= CSeqNo::seqlen(m_piData1[i], m_piData2[loc]);
00232 
00233                m_piData2[loc] = m_piData2[i];
00234             }
00235             else
00236                m_iLength -= CSeqNo::seqlen(m_piData1[i], m_piData2[i]);
00237          }
00238          else
00239          {
00240             if (m_piData1[i] == CSeqNo::incseq(m_piData2[loc]))
00241                m_piData2[loc] = m_piData1[i];
00242             else
00243                m_iLength --;
00244          }
00245 
00246          m_piData1[i] = -1;
00247          m_piData2[i] = -1;
00248          m_piNext[loc] = m_piNext[i];
00249       }
00250       else
00251          break;
00252    }
00253 
00254    return m_iLength - origlen;
00255 }
00256 
00257 void CSndLossList::remove(int32_t seqno)
00258 {
00259    CGuard listguard(m_ListLock);
00260 
00261    if (0 == m_iLength)
00262       return;
00263 
00264    // Remove all from the head pointer to a node with a larger seq. no. or the list is empty
00265    int offset = CSeqNo::seqoff(m_piData1[m_iHead], seqno);
00266    int loc = (m_iHead + offset + m_iSize) % m_iSize;
00267 
00268    if (0 == offset)
00269    {
00270       // It is the head. Remove the head and point to the next node
00271       loc = (loc + 1) % m_iSize;
00272 
00273       if (-1 == m_piData2[m_iHead])
00274          loc = m_piNext[m_iHead];
00275       else
00276       {
00277          m_piData1[loc] = CSeqNo::incseq(seqno);
00278          if (CSeqNo::seqcmp(m_piData2[m_iHead], CSeqNo::incseq(seqno)) > 0)
00279             m_piData2[loc] = m_piData2[m_iHead];
00280 
00281          m_piData2[m_iHead] = -1;
00282 
00283          m_piNext[loc] = m_piNext[m_iHead];
00284       }
00285 
00286       m_piData1[m_iHead] = -1;
00287 
00288       if (m_iLastInsertPos == m_iHead)
00289          m_iLastInsertPos = -1;
00290 
00291       m_iHead = loc;
00292 
00293       m_iLength --;
00294    }
00295    else if (offset > 0)
00296    {
00297       int h = m_iHead;
00298 
00299       if (seqno == m_piData1[loc])
00300       {
00301          // target node is not empty, remove part/all of the seqno in the node.
00302          int temp = loc;
00303          loc = (loc + 1) % m_iSize;
00304 
00305          if (-1 == m_piData2[temp])
00306             m_iHead = m_piNext[temp];
00307          else
00308          {
00309             // remove part, e.g., [3, 7] becomes [], [4, 7] after remove(3)
00310             m_piData1[loc] = CSeqNo::incseq(seqno);
00311             if (CSeqNo::seqcmp(m_piData2[temp], m_piData1[loc]) > 0)
00312                m_piData2[loc] = m_piData2[temp];
00313             m_iHead = loc;
00314             m_piNext[loc] = m_piNext[temp];
00315             m_piNext[temp] = loc;
00316             m_piData2[temp] = -1;
00317          }
00318       }
00319       else
00320       {
00321          // target node is empty, check prior node
00322          int i = m_iHead;
00323          while ((-1 != m_piNext[i]) && (CSeqNo::seqcmp(m_piData1[m_piNext[i]], seqno) < 0))
00324             i = m_piNext[i];
00325 
00326          loc = (loc + 1) % m_iSize;
00327 
00328          if (-1 == m_piData2[i])
00329             m_iHead = m_piNext[i];
00330          else if (CSeqNo::seqcmp(m_piData2[i], seqno) > 0)
00331          {
00332             // remove part/all seqno in the prior node
00333             m_piData1[loc] = CSeqNo::incseq(seqno);
00334             if (CSeqNo::seqcmp(m_piData2[i], m_piData1[loc]) > 0)
00335                m_piData2[loc] = m_piData2[i];
00336 
00337             m_piData2[i] = seqno;
00338 
00339             m_piNext[loc] = m_piNext[i];
00340             m_piNext[i] = loc;
00341 
00342             m_iHead = loc;
00343          }
00344          else
00345             m_iHead = m_piNext[i];
00346       }
00347 
00348       // Remove all nodes prior to the new head
00349       while (h != m_iHead)
00350       {
00351          if (m_piData2[h] != -1)
00352          {
00353             m_iLength -= CSeqNo::seqlen(m_piData1[h], m_piData2[h]);
00354             m_piData2[h] = -1;
00355          }
00356          else
00357             m_iLength --;
00358 
00359          m_piData1[h] = -1;
00360 
00361          if (m_iLastInsertPos == h)
00362             m_iLastInsertPos = -1;
00363 
00364          h = m_piNext[h];
00365       }
00366    }
00367 }
00368 
00369 int CSndLossList::getLossLength()
00370 {
00371    CGuard listguard(m_ListLock);
00372 
00373    return m_iLength;
00374 }
00375 
00376 int32_t CSndLossList::getLostSeq()
00377 {
00378    if (0 == m_iLength)
00379      return -1;
00380 
00381    CGuard listguard(m_ListLock);
00382 
00383    if (0 == m_iLength)
00384      return -1;
00385 
00386    if (m_iLastInsertPos == m_iHead)
00387       m_iLastInsertPos = -1;
00388 
00389    // return the first loss seq. no.
00390    int32_t seqno = m_piData1[m_iHead];
00391 
00392    // head moves to the next node
00393    if (-1 == m_piData2[m_iHead])
00394    {
00395       //[3, -1] becomes [], and head moves to next node in the list
00396       m_piData1[m_iHead] = -1;
00397       m_iHead = m_piNext[m_iHead];
00398    }
00399    else
00400    {
00401       // shift to next node, e.g., [3, 7] becomes [], [4, 7]
00402       int loc = (m_iHead + 1) % m_iSize;
00403 
00404       m_piData1[loc] = CSeqNo::incseq(seqno);
00405       if (CSeqNo::seqcmp(m_piData2[m_iHead], m_piData1[loc]) > 0)
00406          m_piData2[loc] = m_piData2[m_iHead];
00407 
00408       m_piData1[m_iHead] = -1;
00409       m_piData2[m_iHead] = -1;
00410 
00411       m_piNext[loc] = m_piNext[m_iHead];
00412       m_iHead = loc;
00413    }
00414 
00415    m_iLength --;
00416 
00417    return seqno;
00418 }
00419 
00421 
00422 CRcvLossList::CRcvLossList(int size):
00423 m_piData1(NULL),
00424 m_piData2(NULL),
00425 m_piNext(NULL),
00426 m_piPrior(NULL),
00427 m_iHead(-1),
00428 m_iTail(-1),
00429 m_iLength(0),
00430 m_iSize(size)
00431 {
00432    m_piData1 = new int32_t [m_iSize];
00433    m_piData2 = new int32_t [m_iSize];
00434    m_piNext = new int [m_iSize];
00435    m_piPrior = new int [m_iSize];
00436 
00437    // -1 means there is no data in the node
00438    for (int i = 0; i < size; ++ i)
00439    {
00440       m_piData1[i] = -1;
00441       m_piData2[i] = -1;
00442    }
00443 }
00444 
00445 CRcvLossList::~CRcvLossList()
00446 {
00447    delete [] m_piData1;
00448    delete [] m_piData2;
00449    delete [] m_piNext;
00450    delete [] m_piPrior;
00451 }
00452 
00453 void CRcvLossList::insert(int32_t seqno1, int32_t seqno2)
00454 {
00455    // Data to be inserted must be larger than all those in the list
00456    // guaranteed by the UDT receiver
00457 
00458    if (0 == m_iLength)
00459    {
00460       // insert data into an empty list
00461       m_iHead = 0;
00462       m_iTail = 0;
00463       m_piData1[m_iHead] = seqno1;
00464       if (seqno2 != seqno1)
00465          m_piData2[m_iHead] = seqno2;
00466 
00467       m_piNext[m_iHead] = -1;
00468       m_piPrior[m_iHead] = -1;
00469       m_iLength += CSeqNo::seqlen(seqno1, seqno2);
00470 
00471       return;
00472    }
00473 
00474    // otherwise searching for the position where the node should be
00475    int offset = CSeqNo::seqoff(m_piData1[m_iHead], seqno1);
00476    int loc = (m_iHead + offset) % m_iSize;
00477 
00478    if ((-1 != m_piData2[m_iTail]) && (CSeqNo::incseq(m_piData2[m_iTail]) == seqno1))
00479    {
00480       // coalesce with prior node, e.g., [2, 5], [6, 7] becomes [2, 7]
00481       loc = m_iTail;
00482       m_piData2[loc] = seqno2;
00483    }
00484    else
00485    {
00486       // create new node
00487       m_piData1[loc] = seqno1;
00488 
00489       if (seqno2 != seqno1)
00490          m_piData2[loc] = seqno2;
00491 
00492       m_piNext[m_iTail] = loc;
00493       m_piPrior[loc] = m_iTail;
00494       m_piNext[loc] = -1;
00495       m_iTail = loc;
00496    }
00497 
00498    m_iLength += CSeqNo::seqlen(seqno1, seqno2);
00499 }
00500 
00501 bool CRcvLossList::remove(int32_t seqno)
00502 {
00503    if (0 == m_iLength)
00504       return false; 
00505 
00506    // locate the position of "seqno" in the list
00507    int offset = CSeqNo::seqoff(m_piData1[m_iHead], seqno);
00508    if (offset < 0)
00509       return false;
00510 
00511    int loc = (m_iHead + offset) % m_iSize;
00512 
00513    if (seqno == m_piData1[loc])
00514    {
00515       // This is a seq. no. that starts the loss sequence
00516 
00517       if (-1 == m_piData2[loc])
00518       {
00519          // there is only 1 loss in the sequence, delete it from the node
00520          if (m_iHead == loc)
00521          {
00522             m_iHead = m_piNext[m_iHead];
00523             if (-1 != m_iHead)
00524                m_piPrior[m_iHead] = -1;
00525          }
00526          else
00527          {
00528             m_piNext[m_piPrior[loc]] = m_piNext[loc];
00529             if (-1 != m_piNext[loc])
00530                m_piPrior[m_piNext[loc]] = m_piPrior[loc];
00531             else
00532                m_iTail = m_piPrior[loc];
00533          }
00534 
00535          m_piData1[loc] = -1;
00536       }
00537       else
00538       {
00539          // there are more than 1 loss in the sequence
00540          // move the node to the next and update the starter as the next loss inSeqNo(seqno)
00541 
00542          // find next node
00543          int i = (loc + 1) % m_iSize;
00544 
00545          // remove the "seqno" and change the starter as next seq. no.
00546          m_piData1[i] = CSeqNo::incseq(m_piData1[loc]);
00547 
00548          // process the sequence end
00549          if (CSeqNo::seqcmp(m_piData2[loc], CSeqNo::incseq(m_piData1[loc])) > 0)
00550             m_piData2[i] = m_piData2[loc];
00551 
00552          // remove the current node
00553          m_piData1[loc] = -1;
00554          m_piData2[loc] = -1;
00555  
00556          // update list pointer
00557          m_piNext[i] = m_piNext[loc];
00558          m_piPrior[i] = m_piPrior[loc];
00559 
00560          if (m_iHead == loc)
00561             m_iHead = i;
00562          else
00563             m_piNext[m_piPrior[i]] = i;
00564 
00565          if (m_iTail == loc)
00566             m_iTail = i;
00567          else
00568             m_piPrior[m_piNext[i]] = i;
00569       }
00570 
00571       m_iLength --;
00572 
00573       return true;
00574    }
00575 
00576    // There is no loss sequence in the current position
00577    // the "seqno" may be contained in a previous node
00578 
00579    // searching previous node
00580    int i = (loc - 1 + m_iSize) % m_iSize;
00581    while (-1 == m_piData1[i])
00582       i = (i - 1 + m_iSize) % m_iSize;
00583 
00584    // not contained in this node, return
00585    if ((-1 == m_piData2[i]) || (CSeqNo::seqcmp(seqno, m_piData2[i]) > 0))
00586        return false;
00587 
00588    if (seqno == m_piData2[i])
00589    {
00590       // it is the sequence end
00591 
00592       if (seqno == CSeqNo::incseq(m_piData1[i]))
00593          m_piData2[i] = -1;
00594       else
00595          m_piData2[i] = CSeqNo::decseq(seqno);
00596    }
00597    else
00598    {
00599       // split the sequence
00600 
00601       // construct the second sequence from CSeqNo::incseq(seqno) to the original sequence end
00602       // located at "loc + 1"
00603       loc = (loc + 1) % m_iSize;
00604 
00605       m_piData1[loc] = CSeqNo::incseq(seqno);
00606       if (CSeqNo::seqcmp(m_piData2[i], m_piData1[loc]) > 0)      
00607          m_piData2[loc] = m_piData2[i];
00608 
00609       // the first (original) sequence is between the original sequence start to CSeqNo::decseq(seqno)
00610       if (seqno == CSeqNo::incseq(m_piData1[i]))
00611          m_piData2[i] = -1;
00612       else
00613          m_piData2[i] = CSeqNo::decseq(seqno);
00614 
00615       // update the list pointer
00616       m_piNext[loc] = m_piNext[i];
00617       m_piNext[i] = loc;
00618       m_piPrior[loc] = i;
00619 
00620       if (m_iTail == i)
00621          m_iTail = loc;
00622       else
00623          m_piPrior[m_piNext[loc]] = loc;
00624    }
00625 
00626    m_iLength --;
00627 
00628    return true;
00629 }
00630 
00631 bool CRcvLossList::remove(int32_t seqno1, int32_t seqno2)
00632 {
00633    if (seqno1 <= seqno2)
00634    {
00635       for (int32_t i = seqno1; i <= seqno2; ++ i)
00636          remove(i);
00637    }
00638    else
00639    {
00640       for (int32_t j = seqno1; j < CSeqNo::m_iMaxSeqNo; ++ j)
00641          remove(j);
00642       for (int32_t k = 0; k <= seqno2; ++ k)
00643          remove(k);
00644    }
00645 
00646    return true;
00647 }
00648 
00649 bool CRcvLossList::find(int32_t seqno1, int32_t seqno2) const
00650 {
00651    if (0 == m_iLength)
00652       return false;
00653 
00654    int p = m_iHead;
00655 
00656    while (-1 != p)
00657    {
00658       if ((CSeqNo::seqcmp(m_piData1[p], seqno1) == 0) ||
00659           ((CSeqNo::seqcmp(m_piData1[p], seqno1) > 0) && (CSeqNo::seqcmp(m_piData1[p], seqno2) <= 0)) ||
00660           ((CSeqNo::seqcmp(m_piData1[p], seqno1) < 0) && (m_piData2[p] != -1) && CSeqNo::seqcmp(m_piData2[p], seqno1) >= 0))
00661           return true;
00662 
00663       p = m_piNext[p];
00664    }
00665 
00666    return false;
00667 }
00668 
00669 int CRcvLossList::getLossLength() const
00670 {
00671    return m_iLength;
00672 }
00673 
00674 int CRcvLossList::getFirstLostSeq() const
00675 {
00676    if (0 == m_iLength)
00677       return -1;
00678 
00679    return m_piData1[m_iHead];
00680 }
00681 
00682 void CRcvLossList::getLossArray(int32_t* array, int& len, int limit)
00683 {
00684    len = 0;
00685 
00686    int i = m_iHead;
00687 
00688    while ((len < limit - 1) && (-1 != i))
00689    {
00690       array[len] = m_piData1[i];
00691       if (-1 != m_piData2[i])
00692       {
00693          // there are more than 1 loss in the sequence
00694          array[len] |= 0x80000000;
00695          ++ len;
00696          array[len] = m_piData2[i];
00697       }
00698 
00699       ++ len;
00700 
00701       i = m_piNext[i];
00702    }
00703 }

Generated on 9 Feb 2013 for barchart-udt-core-2.2.2 by  doxygen 1.6.1