e7f645f444ee3c73a380fb6dde9944242a62d41c
[platform/upstream/libzypp.git] / zypp / Edition.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/Edition.cc
10  *
11 */
12 #include <iostream>
13
14 #include "zypp/base/Logger.h"
15 #include "base/String.h"
16 #include "base/Exception.h"
17
18 #include "zypp/Edition.h"
19
20 using namespace std;
21
22 ///////////////////////////////////////////////////////////////////
23 namespace zypp
24 { /////////////////////////////////////////////////////////////////
25
26   ///////////////////////////////////////////////////////////////////
27   //
28   //    hidden details
29   //
30   ///////////////////////////////////////////////////////////////////
31   namespace
32   {
33     /** Rpm version comparison.
34      * \a lhs and \a rhs are expected to be either version or release
35      * strings. Not both separated by a '-'.
36      * \return <tt>-1,0,1</tt> if version strings are <tt>\<,==,\></tt>
37      * \todo review
38     */
39     int rpmverscmp( const std::string & lhs, const std::string & rhs )
40     {
41       int  num1, num2;
42       char oldch1, oldch2;
43       char * str1, * str2;
44       char * one, * two;
45       int  rc;
46       int  isnum;
47
48       // equal?
49       if ( lhs == rhs )  return 0;
50
51       // empty is less than anything else:
52       if ( lhs.empty() ) return -1;
53       if ( rhs.empty() ) return  1;
54
55       str1 = (char*)alloca( lhs.size() + 1 );
56       str2 = (char*)alloca( rhs.size() + 1 );
57
58       strcpy( str1, lhs.c_str() );
59       strcpy( str2, rhs.c_str() );
60
61       one = str1;
62       two = str2;
63
64       // split strings into segments of alpha or digit
65       // sequences and compare them accordingly.
66       while ( *one && *two ) {
67
68         // skip non alphanumerical chars
69         while ( *one && ! isalnum( *one ) ) ++one;
70         while ( *two && ! isalnum( *two ) ) ++two;
71         if ( ! ( *one && *two ) )
72           break; // reached end of string
73
74         // remember segment start
75         str1 = one;
76         str2 = two;
77
78         // jump over segment, type determined by str1
79         if ( isdigit( *str1 ) ) {
80           while ( isdigit( *str1 ) ) ++str1;
81           while ( isdigit( *str2 ) ) ++str2;
82           isnum = 1;
83         } else {
84           while ( isalpha( *str1 ) ) ++str1;
85           while ( isalpha( *str2 ) ) ++str2;
86           isnum = 0;
87         }
88
89         // one == str1 -> can't be as strings are not empty
90         // two == str2 -> mixed segment types
91         if ( two == str2 ) return( isnum ? 1 : -1 );
92
93         // compare according to segment type
94         if ( isnum ) {
95           // atoi() may overflow on long segments
96           // skip leading zeros
97           while ( *one == '0' ) ++one;
98           while ( *two == '0' ) ++two;
99           // compare number of digits
100           num1 = str1 - one;
101           num2 = str2 - two;
102           if ( num1 != num2 ) return( num1 < num2 ? -1 : 1 );
103         }
104
105         // strcmp() compares alpha AND equal sized number segments
106         // temp. \0-terminate segment
107         oldch1 = *str1;
108         *str1 = '\0';
109         oldch2 = *str2;
110         *str2 = '\0';
111
112         rc = strcmp( one, two );
113         if ( rc ) return rc;
114
115         // restore original strings
116         *str1 = oldch1;
117         *str2 = oldch2;
118
119         // prepare for next cycle
120         one = str1;
121         two = str2;
122       }
123
124       // check which strings are now empty
125       if ( !*one ) {
126         return( !*two ? 0 : -1 );
127       }
128       return 1;
129     }
130   } // namespace
131   ///////////////////////////////////////////////////////////////////
132
133   ///////////////////////////////////////////////////////////////////
134   //
135   //    CLASS NAME : Edition::Impl
136   //
137   /** Edition implementation.
138    * \todo Unifiy Impl in Edition::noedition and Edition::Edition()
139   */
140   struct Edition::Impl
141   {
142     Impl()
143     : _epoch( noepoch )
144     {}
145
146     Impl( const std::string & edition_r )
147     : _epoch( noepoch )
148     {
149       str::smatch what;
150       if( str::regex_match( edition_r.begin(), edition_r.end(),
151                             what, _rxEdition ) )
152         {
153           // what[2] contains the epoch
154           // what[3] contains the version
155           // what[5] contains the release
156           if ( what[2].matched )
157             _epoch = strtoul( what[2].str().c_str(), NULL, 10 );
158           if ( what[3].matched )
159             _version = what[3].str();
160           if ( what[5].matched )
161             _release = what[5].str();
162         }
163       else
164         {
165           ZYPP_THROW( Exception(string("Invalid Edition: ")+edition_r) );
166         }
167     }
168
169     Impl( const std::string & version_r,
170           const std::string & release_r,
171           epoch_t epoch_r )
172     : _epoch( epoch_r )
173     , _version( validateVR(version_r) )
174     , _release( validateVR(release_r) )
175     {}
176
177     Impl( const std::string & version_r,
178           const std::string & release_r,
179           const std::string & epoch_r )
180     : _epoch( validateE(epoch_r) )
181     , _version( validateVR(version_r) )
182     , _release( validateVR(release_r) )
183     {}
184
185     /** Dtor */
186     ~Impl()
187     {}
188
189     /** return validated epoch ([0-9]*) or throw */
190     static epoch_t validateE( const std::string & epoch_r )
191     {
192       if ( epoch_r.empty() )
193         return noepoch;
194
195       char * endptr = NULL;
196       epoch_t ret = strtoul( epoch_r.c_str(), &endptr, 10 );
197       if ( *endptr != '\0' )
198         ZYPP_THROW( Exception(string("Invalid eopch: ")+epoch_r) );
199       return ret;
200     }
201
202     /** return validated version/release or throw */
203     static const std::string & validateVR( const std::string & vr_r )
204     {
205       if ( vr_r.find('-') != string::npos )
206         ZYPP_THROW( Exception(string("Invalid version/release: ")+vr_r) );
207     
208       return vr_r;
209     }
210
211     epoch_t      _epoch;
212     std::string _version;
213     std::string _release;
214
215     static const str::regex _rxEdition;
216   };
217   ///////////////////////////////////////////////////////////////////
218
219   const str::regex Edition::Impl::_rxEdition( "(([0-9]+):)?([^-]*)(-([^-]*))?" );
220
221   ///////////////////////////////////////////////////////////////////
222
223   ///////////////////////////////////////////////////////////////////
224   //
225   //    CLASS NAME : Edition
226   //
227   ///////////////////////////////////////////////////////////////////
228
229   const Edition Edition::noedition;
230
231   ///////////////////////////////////////////////////////////////////
232
233   Edition::Edition()
234   : _pimpl( new Impl )
235   {}
236
237   Edition::Edition( const std::string & edition_r )
238   : _pimpl( new Impl( edition_r ) )
239   {}
240
241   Edition::Edition( const std::string & version_r,
242                     const std::string & release_r,
243                     epoch_t epoch_r )
244   : _pimpl( new Impl( version_r, release_r, epoch_r ) )
245   {}
246
247   Edition::Edition( const std::string & version_r,
248                     const std::string & release_r,
249                     const std::string & epoch_r )
250   : _pimpl( new Impl( version_r, release_r, epoch_r ) )
251   {}
252
253   Edition::~Edition()
254   {}
255
256   Edition::epoch_t Edition::epoch() const
257   { return _pimpl->_epoch; }
258
259   const std::string & Edition::version() const
260   { return _pimpl->_version; }
261
262   const std::string & Edition::release() const
263   { return _pimpl->_release; }
264
265   std::string Edition::asString() const
266   {
267     string ret;
268
269     if ( _pimpl->_epoch )
270       ret += str::form(  "%d:", _pimpl->_epoch );
271
272     ret += _pimpl->_version;
273
274     if ( ! _pimpl->_release.empty() )
275       {
276         ret += '-';
277         ret += _pimpl->_release;
278       }
279
280     if ( ret.empty() )
281       return "";
282
283     return ret;
284   }
285
286   int Edition::compare( const Edition & lhs, const Edition & rhs )
287   {
288     // compare epoch
289     if ( lhs.epoch() != rhs.epoch() )
290       return lhs.epoch() < rhs.epoch() ? -1 : 1;
291
292     // next compare versions
293     int res = rpmverscmp( lhs.version(), rhs.version() );
294     if ( res )
295       return res; // -1|1: not equal
296
297     return rpmverscmp( lhs.release(), rhs.release() );
298   }
299
300   int Edition::match( const Edition & lhs, const Edition & rhs )
301   {
302     // compare epoch
303     if ( lhs.epoch() != rhs.epoch() )
304       return lhs.epoch() < rhs.epoch() ? -1 : 1;
305
306     // next compare versions
307     if ( lhs.version().empty() || rhs.version().empty() )
308       return 0; //equal
309
310     int res = rpmverscmp( lhs.version(), rhs.version() );
311     if ( res )
312       return res; // -1|1: not equal
313
314     // finaly compare releases
315     if ( lhs.release().empty() || rhs.release().empty() )
316       return 0; //equal
317
318     return rpmverscmp( lhs.release(), rhs.release() );
319   }
320
321   /////////////////////////////////////////////////////////////////
322 } // namespace zypp
323 ///////////////////////////////////////////////////////////////////
324
325