load.cpp
Go to the documentation of this file.
00001 /*************************************************************************** 00002 file : $URL: http://svn.code.sf.net/p/frepple/code/trunk/src/model/load.cpp $ 00003 version : $LastChangedRevision: 1713 $ $LastChangedBy: jdetaeye $ 00004 date : $LastChangedDate: 2012-07-18 11:46:01 +0200 (Wed, 18 Jul 2012) $ 00005 ***************************************************************************/ 00006 00007 /*************************************************************************** 00008 * * 00009 * Copyright (C) 2007-2012 by Johan De Taeye, frePPLe bvba * 00010 * * 00011 * This library is free software; you can redistribute it and/or modify it * 00012 * under the terms of the GNU Affero General Public License as published * 00013 * by the Free Software Foundation; either version 3 of the License, or * 00014 * (at your option) any later version. * 00015 * * 00016 * This library is distributed in the hope that it will be useful, * 00017 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 00019 * GNU Affero General Public License for more details. * 00020 * * 00021 * You should have received a copy of the GNU Affero General Public * 00022 * License along with this program. * 00023 * If not, see <http://www.gnu.org/licenses/>. * 00024 * * 00025 ***************************************************************************/ 00026 00027 #define FREPPLE_CORE 00028 #include "frepple/model.h" 00029 namespace frepple 00030 { 00031 00032 DECLARE_EXPORT const MetaCategory* Load::metadata; 00033 00034 00035 int Load::initialize() 00036 { 00037 // Initialize the metadata 00038 metadata = new MetaCategory 00039 ("load", "loads", MetaCategory::ControllerDefault, writer); 00040 const_cast<MetaCategory*>(metadata)->registerClass( 00041 "load","load",true,Object::createDefault<Load> 00042 ); 00043 00044 // Initialize the Python class 00045 PythonType& x = FreppleCategory<Load>::getType(); 00046 x.setName("load"); 00047 x.setDoc("frePPLe load"); 00048 x.supportgetattro(); 00049 x.supportsetattro(); 00050 x.supportcreate(create); 00051 x.addMethod("toXML", toXML, METH_VARARGS, "return a XML representation"); 00052 const_cast<MetaCategory*>(Load::metadata)->pythonClass = x.type_object(); 00053 return x.typeReady(); 00054 } 00055 00056 00057 void Load::writer(const MetaCategory* c, XMLOutput* o) 00058 { 00059 bool firstload = true; 00060 for (Operation::iterator i = Operation::begin(); i != Operation::end(); ++i) 00061 for (Operation::loadlist::const_iterator j = i->getLoads().begin(); j != i->getLoads().end(); ++j) 00062 { 00063 if (firstload) 00064 { 00065 o->BeginObject(Tags::tag_loads); 00066 firstload = false; 00067 } 00068 // We use the FULL mode, to force the loads being written regardless 00069 // of the depth in the XML tree. 00070 o->writeElement(Tags::tag_load, &*j, FULL); 00071 } 00072 if (!firstload) o->EndObject(Tags::tag_loads); 00073 } 00074 00075 00076 DECLARE_EXPORT void Load::validate(Action action) 00077 { 00078 // Catch null operation and resource pointers 00079 Operation *oper = getOperation(); 00080 Resource *res = getResource(); 00081 if (!oper || !res) 00082 { 00083 // Invalid load model 00084 if (!oper && !res) 00085 throw DataException("Missing operation and resource on a load"); 00086 else if (!oper) 00087 throw DataException("Missing operation on a load on resource '" 00088 + res->getName() + "'"); 00089 else if (!res) 00090 throw DataException("Missing resource on a load on operation '" 00091 + oper->getName() + "'"); 00092 } 00093 00094 // Check if a load with 1) identical resource, 2) identical operation and 00095 // 3) overlapping effectivity dates already exists 00096 Operation::loadlist::const_iterator i = oper->getLoads().begin(); 00097 for (; i != oper->getLoads().end(); ++i) 00098 if (i->getResource() == res 00099 && i->getEffective().overlap(getEffective()) 00100 && &*i != this) 00101 break; 00102 00103 // Apply the appropriate action 00104 switch (action) 00105 { 00106 case ADD: 00107 if (i != oper->getLoads().end()) 00108 { 00109 throw DataException("Load of '" + oper->getName() + "' and '" 00110 + res->getName() + "' already exists"); 00111 } 00112 break; 00113 case CHANGE: 00114 throw DataException("Can't update a load"); 00115 case ADD_CHANGE: 00116 // ADD is handled in the code after the switch statement 00117 if (i == oper->getLoads().end()) break; 00118 throw DataException("Can't update a load"); 00119 case REMOVE: 00120 // This load was only used temporarily during the reading process 00121 delete this; 00122 if (i == oper->getLoads().end()) 00123 // Nothing to delete 00124 throw DataException("Can't remove nonexistent load of '" 00125 + oper->getName() + "' and '" + res->getName() + "'"); 00126 delete &*i; 00127 // Set a flag to make sure the level computation is triggered again 00128 HasLevel::triggerLazyRecomputation(); 00129 return; 00130 } 00131 00132 // The statements below should be executed only when a new load is created. 00133 00134 // If the resource has an owner, also load the owner 00135 // Note that the owner load can create more loads if it has an owner too. 00136 if (res->hasOwner() && action!=REMOVE) new Load(oper, res->getOwner(), qty); 00137 00138 // Set a flag to make sure the level computation is triggered again 00139 HasLevel::triggerLazyRecomputation(); 00140 } 00141 00142 00143 DECLARE_EXPORT Load::~Load() 00144 { 00145 // Set a flag to make sure the level computation is triggered again 00146 HasLevel::triggerLazyRecomputation(); 00147 00148 // Delete existing loadplans 00149 if (getOperation() && getResource()) 00150 { 00151 // Loop over operationplans 00152 for(OperationPlan::iterator i(getOperation()); i != OperationPlan::end(); ++i) 00153 // Loop over loadplans 00154 for(OperationPlan::LoadPlanIterator j = i->beginLoadPlans(); j != i->endLoadPlans(); ) 00155 if (j->getLoad() == this) j.deleteLoadPlan(); 00156 else ++j; 00157 } 00158 00159 // Delete the load from the operation and resource 00160 if (getOperation()) getOperation()->loaddata.erase(this); 00161 if (getResource()) getResource()->loads.erase(this); 00162 00163 // Clean up alternate loads 00164 if (hasAlts) 00165 { 00166 // The load has alternates. 00167 // Make a new load the leading one. Or if there is only one alternate 00168 // present it is not marked as an alternate any more. 00169 unsigned short cnt = 0; 00170 int minprio = INT_MAX; 00171 Load* newLeader = NULL; 00172 for (Operation::loadlist::iterator i = getOperation()->loaddata.begin(); 00173 i != getOperation()->loaddata.end(); ++i) 00174 if (i->altLoad == this) 00175 { 00176 cnt++; 00177 if (i->priority < minprio) 00178 { 00179 newLeader = &*i; 00180 minprio = i->priority; 00181 } 00182 } 00183 if (cnt < 1) 00184 throw LogicException("Alternate loads update failure"); 00185 else if (cnt == 1) 00186 // No longer an alternate any more 00187 newLeader->altLoad = NULL; 00188 else 00189 { 00190 // Mark a new leader load 00191 newLeader->hasAlts = true; 00192 newLeader->altLoad = NULL; 00193 for (Operation::loadlist::iterator i = getOperation()->loaddata.begin(); 00194 i != getOperation()->loaddata.end(); ++i) 00195 if (i->altLoad == this) i->altLoad = newLeader; 00196 } 00197 } 00198 if (altLoad) 00199 { 00200 // The load is an alternate of another one. 00201 // If it was the only alternate, then the hasAlts flag on the parent 00202 // load needs to be set back to false 00203 bool only_one = true; 00204 for (Operation::loadlist::iterator i = getOperation()->loaddata.begin(); 00205 i != getOperation()->loaddata.end(); ++i) 00206 if (i->altLoad == altLoad) 00207 { 00208 only_one = false; 00209 break; 00210 } 00211 if (only_one) altLoad->hasAlts = false; 00212 } 00213 } 00214 00215 00216 DECLARE_EXPORT void Load::setAlternate(Load *f) 00217 { 00218 // Validate the argument 00219 if (!f) 00220 throw DataException("Setting NULL alternate load"); 00221 if (hasAlts || f->altLoad) 00222 throw DataException("Nested alternate loads are not allowed"); 00223 00224 // Update both flows 00225 f->hasAlts = true; 00226 altLoad = f; 00227 } 00228 00229 00230 DECLARE_EXPORT void Load::setAlternate(const string& n) 00231 { 00232 if (!getOperation()) 00233 throw LogicException("Can't set an alternate load before setting the operation"); 00234 Load *x = getOperation()->loaddata.find(n); 00235 if (!x) throw DataException("Can't find load with name '" + n + "'"); 00236 setAlternate(x); 00237 } 00238 00239 00240 DECLARE_EXPORT void Load::setSetup(const string n) 00241 { 00242 setup = n; 00243 00244 if (!setup.empty()) 00245 { 00246 // Guarantuee that only a single load has a setup. 00247 // Alternates of that load can have a setup as well. 00248 for (Operation::loadlist::iterator i = getOperation()->loaddata.begin(); 00249 i != getOperation()->loaddata.end(); ++i) 00250 if (&*i != this && !i->setup.empty() 00251 && i->getAlternate() != this && getAlternate() != &*i 00252 && i->getAlternate() != getAlternate()) 00253 throw DataException("Only a single load of an operation can specify a setup"); 00254 } 00255 } 00256 00257 00258 DECLARE_EXPORT void Load::writeElement(XMLOutput *o, const Keyword& tag, mode m) const 00259 { 00260 // If the load has already been saved, no need to repeat it again 00261 // A 'reference' to a load is not useful to be saved. 00262 if (m == REFERENCE) return; 00263 assert(m != NOHEADER); 00264 00265 o->BeginObject(tag); 00266 00267 // If the load is defined inside of an operation tag, we don't need to save 00268 // the operation. Otherwise we do save it... 00269 if (!dynamic_cast<Operation*>(o->getPreviousObject())) 00270 o->writeElement(Tags::tag_operation, getOperation()); 00271 00272 // If the load is defined inside of an resource tag, we don't need to save 00273 // the resource. Otherwise we do save it... 00274 if (!dynamic_cast<Resource*>(o->getPreviousObject())) 00275 o->writeElement(Tags::tag_resource, getResource()); 00276 00277 // Write the quantity, priority, name and alternate 00278 if (qty != 1.0) o->writeElement(Tags::tag_quantity, qty); 00279 if (getPriority()!=1) o->writeElement(Tags::tag_priority, getPriority()); 00280 if (!getName().empty()) o->writeElement(Tags::tag_name, getName()); 00281 if (getAlternate()) 00282 o->writeElement(Tags::tag_alternate, getAlternate()->getName()); 00283 if (search != PRIORITY) 00284 { 00285 ostringstream ch; 00286 ch << getSearch(); 00287 o->writeElement(Tags::tag_search, ch.str()); 00288 } 00289 00290 // Write the effective daterange 00291 if (getEffective().getStart() != Date::infinitePast) 00292 o->writeElement(Tags::tag_effective_start, getEffective().getStart()); 00293 if (getEffective().getEnd() != Date::infiniteFuture) 00294 o->writeElement(Tags::tag_effective_end, getEffective().getEnd()); 00295 00296 // Write the required setup 00297 if (!setup.empty()) o->writeElement(Tags::tag_setup, setup); 00298 00299 o->EndObject(tag); 00300 } 00301 00302 00303 DECLARE_EXPORT void Load::beginElement(XMLInput& pIn, const Attribute& pAttr) 00304 { 00305 if (pAttr.isA (Tags::tag_resource)) 00306 pIn.readto( Resource::reader(Resource::metadata,pIn.getAttributes()) ); 00307 else if (pAttr.isA (Tags::tag_operation)) 00308 pIn.readto( Operation::reader(Operation::metadata,pIn.getAttributes()) ); 00309 } 00310 00311 00312 DECLARE_EXPORT void Load::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement) 00313 { 00314 if (pAttr.isA (Tags::tag_resource)) 00315 { 00316 Resource * r = dynamic_cast<Resource*>(pIn.getPreviousObject()); 00317 if (r) setResource(r); 00318 else throw LogicException("Incorrect object type during read operation"); 00319 } 00320 else if (pAttr.isA (Tags::tag_operation)) 00321 { 00322 Operation * o = dynamic_cast<Operation*>(pIn.getPreviousObject()); 00323 if (o) setOperation(o); 00324 else throw LogicException("Incorrect object type during read operation"); 00325 } 00326 else if (pAttr.isA(Tags::tag_quantity)) 00327 setQuantity(pElement.getDouble()); 00328 else if (pAttr.isA(Tags::tag_priority)) 00329 setPriority(pElement.getInt()); 00330 else if (pAttr.isA(Tags::tag_name)) 00331 setName(pElement.getString()); 00332 else if (pAttr.isA(Tags::tag_alternate)) 00333 setAlternate(pElement.getString()); 00334 else if (pAttr.isA(Tags::tag_search)) 00335 setSearch(pElement.getString()); 00336 else if (pAttr.isA(Tags::tag_setup)) 00337 setSetup(pElement.getString()); 00338 else if (pAttr.isA(Tags::tag_action)) 00339 { 00340 delete static_cast<Action*>(pIn.getUserArea()); 00341 pIn.setUserArea( 00342 new Action(MetaClass::decodeAction(pElement.getString().c_str())) 00343 ); 00344 } 00345 else if (pAttr.isA(Tags::tag_effective_end)) 00346 setEffectiveEnd(pElement.getDate()); 00347 else if (pAttr.isA(Tags::tag_effective_start)) 00348 setEffectiveStart(pElement.getDate()); 00349 else if (pIn.isObjectEnd()) 00350 { 00351 // The load data is now all read in. See if it makes sense now... 00352 try 00353 { 00354 validate(!pIn.getUserArea() ? 00355 ADD_CHANGE : 00356 *static_cast<Action*>(pIn.getUserArea()) 00357 ); 00358 } 00359 catch (...) 00360 { 00361 delete this; 00362 throw; 00363 } 00364 delete static_cast<Action*>(pIn.getUserArea()); 00365 } 00366 } 00367 00368 00369 DECLARE_EXPORT PyObject* Load::getattro(const Attribute& attr) 00370 { 00371 if (attr.isA(Tags::tag_resource)) 00372 return PythonObject(getResource()); 00373 if (attr.isA(Tags::tag_operation)) 00374 return PythonObject(getOperation()); 00375 if (attr.isA(Tags::tag_quantity)) 00376 return PythonObject(getQuantity()); 00377 if (attr.isA(Tags::tag_priority)) 00378 return PythonObject(getPriority()); 00379 if (attr.isA(Tags::tag_effective_end)) 00380 return PythonObject(getEffective().getEnd()); 00381 if (attr.isA(Tags::tag_effective_start)) 00382 return PythonObject(getEffective().getStart()); 00383 if (attr.isA(Tags::tag_name)) 00384 return PythonObject(getName()); 00385 if (attr.isA(Tags::tag_hidden)) 00386 return PythonObject(getHidden()); 00387 if (attr.isA(Tags::tag_alternate)) 00388 return PythonObject(getAlternate()); 00389 if (attr.isA(Tags::tag_search)) 00390 { 00391 ostringstream ch; 00392 ch << getSearch(); 00393 return PythonObject(ch.str()); 00394 } 00395 if (attr.isA(Tags::tag_setup)) 00396 return PythonObject(getSetup()); 00397 return NULL; 00398 } 00399 00400 00401 DECLARE_EXPORT int Load::setattro(const Attribute& attr, const PythonObject& field) 00402 { 00403 if (attr.isA(Tags::tag_resource)) 00404 { 00405 if (!field.check(Resource::metadata)) 00406 { 00407 PyErr_SetString(PythonDataException, "load resource must be of type resource"); 00408 return -1; 00409 } 00410 Resource* y = static_cast<Resource*>(static_cast<PyObject*>(field)); 00411 setResource(y); 00412 } 00413 else if (attr.isA(Tags::tag_operation)) 00414 { 00415 if (!field.check(Operation::metadata)) 00416 { 00417 PyErr_SetString(PythonDataException, "load operation must be of type operation"); 00418 return -1; 00419 } 00420 Operation* y = static_cast<Operation*>(static_cast<PyObject*>(field)); 00421 setOperation(y); 00422 } 00423 else if (attr.isA(Tags::tag_quantity)) 00424 setQuantity(field.getDouble()); 00425 else if (attr.isA(Tags::tag_priority)) 00426 setPriority(field.getInt()); 00427 else if (attr.isA(Tags::tag_effective_end)) 00428 setEffectiveEnd(field.getDate()); 00429 else if (attr.isA(Tags::tag_effective_start)) 00430 setEffectiveStart(field.getDate()); 00431 else if (attr.isA(Tags::tag_name)) 00432 setName(field.getString()); 00433 else if (attr.isA(Tags::tag_alternate)) 00434 { 00435 if (!field.check(Load::metadata)) 00436 setAlternate(field.getString()); 00437 else 00438 { 00439 Load *y = static_cast<Load*>(static_cast<PyObject*>(field)); 00440 setAlternate(y); 00441 } 00442 } 00443 else if (attr.isA(Tags::tag_search)) 00444 setSearch(field.getString()); 00445 else if (attr.isA(Tags::tag_setup)) 00446 setSetup(field.getString()); 00447 else 00448 return -1; 00449 return 0; 00450 } 00451 00452 00453 /** @todo this method implementation is not generic enough and not extendible by subclasses. */ 00454 PyObject* Load::create(PyTypeObject* pytype, PyObject* args, PyObject* kwds) 00455 { 00456 try 00457 { 00458 // Pick up the operation 00459 PyObject* oper = PyDict_GetItemString(kwds,"operation"); 00460 if (!PyObject_TypeCheck(oper, Operation::metadata->pythonClass)) 00461 throw DataException("load operation must be of type operation"); 00462 00463 // Pick up the resource 00464 PyObject* res = PyDict_GetItemString(kwds,"resource"); 00465 if (!PyObject_TypeCheck(res, Resource::metadata->pythonClass)) 00466 throw DataException("load resource must be of type resource"); 00467 00468 // Pick up the quantity 00469 PyObject* q1 = PyDict_GetItemString(kwds,"quantity"); 00470 double q2 = q1 ? PythonObject(q1).getDouble() : 1.0; 00471 00472 // Pick up the effective dates 00473 DateRange eff; 00474 PyObject* eff_start = PyDict_GetItemString(kwds,"effective_start"); 00475 if (eff_start) 00476 { 00477 PythonObject d(eff_start); 00478 eff.setStart(d.getDate()); 00479 } 00480 PyObject* eff_end = PyDict_GetItemString(kwds,"effective_end"); 00481 if (eff_end) 00482 { 00483 PythonObject d(eff_end); 00484 eff.setEnd(d.getDate()); 00485 } 00486 00487 // Create the load 00488 Load *l = new Load( 00489 static_cast<Operation*>(oper), 00490 static_cast<Resource*>(res), 00491 q2, eff 00492 ); 00493 00494 // Return the object 00495 Py_INCREF(l); 00496 return static_cast<PyObject*>(l); 00497 } 00498 catch (...) 00499 { 00500 PythonType::evalException(); 00501 return NULL; 00502 } 00503 } 00504 00505 00506 int LoadIterator::initialize() 00507 { 00508 // Initialize the type 00509 PythonType& x = PythonExtension<LoadIterator>::getType(); 00510 x.setName("loadIterator"); 00511 x.setDoc("frePPLe iterator for loads"); 00512 x.supportiter(); 00513 return x.typeReady(); 00514 } 00515 00516 00517 PyObject* LoadIterator::iternext() 00518 { 00519 PyObject* result; 00520 if (res) 00521 { 00522 // Iterate over loads on a resource 00523 if (ir == res->getLoads().end()) return NULL; 00524 result = const_cast<Load*>(&*ir); 00525 ++ir; 00526 } 00527 else 00528 { 00529 // Iterate over loads on an operation 00530 if (io == oper->getLoads().end()) return NULL; 00531 result = const_cast<Load*>(&*io); 00532 ++io; 00533 } 00534 Py_INCREF(result); 00535 return result; 00536 } 00537 00538 } // end namespace