load.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2013 by Johan De Taeye, frePPLe bvba *
4  * *
5  * This library is free software; you can redistribute it and/or modify it *
6  * under the terms of the GNU Affero General Public License as published *
7  * by the Free Software Foundation; either version 3 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU Affero General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Affero General Public *
16  * License along with this program. *
17  * If not, see <http://www.gnu.org/licenses/>. *
18  * *
19  ***************************************************************************/
20 
21 #define FREPPLE_CORE
22 #include "frepple/model.h"
23 namespace frepple
24 {
25 
27 
28 
30 {
31  // Initialize the metadata
33  ("load", "loads", MetaCategory::ControllerDefault, writer);
34  const_cast<MetaCategory*>(metadata)->registerClass(
35  "load","load",true,Object::createDefault<Load>
36  );
37 
38  // Initialize the Python class
40  x.setName("load");
41  x.setDoc("frePPLe load");
42  x.supportgetattro();
43  x.supportsetattro();
44  x.supportcreate(create);
45  x.addMethod("toXML", toXML, METH_VARARGS, "return a XML representation");
46  const_cast<MetaCategory*>(Load::metadata)->pythonClass = x.type_object();
47  return x.typeReady();
48 }
49 
50 
52 {
53  bool firstload = true;
54  for (Operation::iterator i = Operation::begin(); i != Operation::end(); ++i)
55  for (Operation::loadlist::const_iterator j = i->getLoads().begin(); j != i->getLoads().end(); ++j)
56  {
57  if (firstload)
58  {
60  firstload = false;
61  }
62  // We use the FULL mode, to force the loads being written regardless
63  // of the depth in the XML tree.
65  }
66  if (!firstload) o->EndObject(Tags::tag_loads);
67 }
68 
69 
70 DECLARE_EXPORT void Load::validate(Action action)
71 {
72  // Catch null operation and resource pointers
73  Operation *oper = getOperation();
74  Resource *res = getResource();
75  if (!oper || !res)
76  {
77  // Invalid load model
78  if (!oper && !res)
79  throw DataException("Missing operation and resource on a load");
80  else if (!oper)
81  throw DataException("Missing operation on a load on resource '"
82  + res->getName() + "'");
83  else if (!res)
84  throw DataException("Missing resource on a load on operation '"
85  + oper->getName() + "'");
86  }
87 
88  // Check if a load with 1) identical resource, 2) identical operation and
89  // 3) overlapping effectivity dates already exists
90  Operation::loadlist::const_iterator i = oper->getLoads().begin();
91  for (; i != oper->getLoads().end(); ++i)
92  if (i->getResource() == res
93  && i->getEffective().overlap(getEffective())
94  && &*i != this)
95  break;
96 
97  // Apply the appropriate action
98  switch (action)
99  {
100  case ADD:
101  if (i != oper->getLoads().end())
102  {
103  throw DataException("Load of '" + oper->getName() + "' and '"
104  + res->getName() + "' already exists");
105  }
106  break;
107  case CHANGE:
108  throw DataException("Can't update a load");
109  case ADD_CHANGE:
110  // ADD is handled in the code after the switch statement
111  if (i == oper->getLoads().end()) break;
112  throw DataException("Can't update a load");
113  case REMOVE:
114  // This load was only used temporarily during the reading process
115  delete this;
116  if (i == oper->getLoads().end())
117  // Nothing to delete
118  throw DataException("Can't remove nonexistent load of '"
119  + oper->getName() + "' and '" + res->getName() + "'");
120  delete &*i;
121  // Set a flag to make sure the level computation is triggered again
123  return;
124  }
125 
126  // The statements below should be executed only when a new load is created.
127 
128  // Set a flag to make sure the level computation is triggered again
130 }
131 
132 
134 {
135  // Set a flag to make sure the level computation is triggered again
137 
138  // Delete existing loadplans
139  if (getOperation() && getResource())
140  {
141  // Loop over operationplans
143  // Loop over loadplans
144  for(OperationPlan::LoadPlanIterator j = i->beginLoadPlans(); j != i->endLoadPlans(); )
145  if (j->getLoad() == this) j.deleteLoadPlan();
146  else ++j;
147  }
148 
149  // Delete the load from the operation and resource
150  if (getOperation()) getOperation()->loaddata.erase(this);
151  if (getResource()) getResource()->loads.erase(this);
152 
153  // Clean up alternate loads
154  if (hasAlts)
155  {
156  // The load has alternates.
157  // Make a new load the leading one. Or if there is only one alternate
158  // present it is not marked as an alternate any more.
159  unsigned short cnt = 0;
160  int minprio = INT_MAX;
161  Load* newLeader = NULL;
162  for (Operation::loadlist::iterator i = getOperation()->loaddata.begin();
163  i != getOperation()->loaddata.end(); ++i)
164  if (i->altLoad == this)
165  {
166  cnt++;
167  if (i->priority < minprio)
168  {
169  newLeader = &*i;
170  minprio = i->priority;
171  }
172  }
173  if (cnt < 1)
174  throw LogicException("Alternate loads update failure");
175  else if (cnt == 1)
176  // No longer an alternate any more
177  newLeader->altLoad = NULL;
178  else
179  {
180  // Mark a new leader load
181  newLeader->hasAlts = true;
182  newLeader->altLoad = NULL;
183  for (Operation::loadlist::iterator i = getOperation()->loaddata.begin();
184  i != getOperation()->loaddata.end(); ++i)
185  if (i->altLoad == this) i->altLoad = newLeader;
186  }
187  }
188  if (altLoad)
189  {
190  // The load is an alternate of another one.
191  // If it was the only alternate, then the hasAlts flag on the parent
192  // load needs to be set back to false
193  bool only_one = true;
194  for (Operation::loadlist::iterator i = getOperation()->loaddata.begin();
195  i != getOperation()->loaddata.end(); ++i)
196  if (i->altLoad == altLoad)
197  {
198  only_one = false;
199  break;
200  }
201  if (only_one) altLoad->hasAlts = false;
202  }
203 }
204 
205 
207 {
208  // Validate the argument
209  if (!f)
210  throw DataException("Setting NULL alternate load");
211  if (hasAlts || f->altLoad)
212  throw DataException("Nested alternate loads are not allowed");
213 
214  // Update both flows
215  f->hasAlts = true;
216  altLoad = f;
217 }
218 
219 
220 DECLARE_EXPORT void Load::setAlternate(const string& n)
221 {
222  if (!getOperation())
223  throw LogicException("Can't set an alternate load before setting the operation");
224  Load *x = getOperation()->loaddata.find(n);
225  if (!x) throw DataException("Can't find load with name '" + n + "'");
226  setAlternate(x);
227 }
228 
229 
230 DECLARE_EXPORT void Load::setSetup(const string n)
231 {
232  setup = n;
233 
234  if (!setup.empty())
235  {
236  // Guarantuee that only a single load has a setup.
237  // Alternates of that load can have a setup as well.
238  for (Operation::loadlist::iterator i = getOperation()->loaddata.begin();
239  i != getOperation()->loaddata.end(); ++i)
240  if (&*i != this && !i->setup.empty()
241  && i->getAlternate() != this && getAlternate() != &*i
242  && i->getAlternate() != getAlternate())
243  throw DataException("Only a single load of an operation can specify a setup");
244  }
245 }
246 
247 
249 {
250  // If the load has already been saved, no need to repeat it again
251  // A 'reference' to a load is not useful to be saved.
252  if (m == REFERENCE) return;
253  assert(m != NOHEAD && m != NOHEADTAIL);
254 
255  o->BeginObject(tag);
256 
257  // If the load is defined inside of an operation tag, we don't need to save
258  // the operation. Otherwise we do save it...
259  if (!dynamic_cast<Operation*>(o->getPreviousObject()))
261 
262  // If the load is defined inside of an resource tag, we don't need to save
263  // the resource. Otherwise we do save it...
264  if (!dynamic_cast<Resource*>(o->getPreviousObject()))
266 
267  // Write the quantity, priority, name and alternate
268  if (qty != 1.0) o->writeElement(Tags::tag_quantity, qty);
270  if (!getName().empty()) o->writeElement(Tags::tag_name, getName());
271  if (getAlternate())
273  if (search != PRIORITY)
274  {
275  ostringstream ch;
276  ch << getSearch();
277  o->writeElement(Tags::tag_search, ch.str());
278  }
279 
280  // Write the effective daterange
281  if (getEffective().getStart() != Date::infinitePast)
283  if (getEffective().getEnd() != Date::infiniteFuture)
285 
286  // Write the required setup
287  if (!setup.empty()) o->writeElement(Tags::tag_setup, setup);
288 
289  // Write the required skill
290  if (skill) o->writeElement(Tags::tag_skill, skill);
291 
292  // Write the tail
293  if (m != NOHEADTAIL && m != NOTAIL) o->EndObject(tag);
294 }
295 
296 
298 {
299  if (pAttr.isA (Tags::tag_resource))
301  else if (pAttr.isA (Tags::tag_operation))
303  else if (pAttr.isA (Tags::tag_skill))
305 }
306 
307 
308 DECLARE_EXPORT void Load::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
309 {
310  if (pAttr.isA (Tags::tag_resource))
311  {
312  Resource *r = dynamic_cast<Resource*>(pIn.getPreviousObject());
313  if (r) setResource(r);
314  else throw LogicException("Incorrect object type during read operation");
315  }
316  else if (pAttr.isA (Tags::tag_operation))
317  {
318  Operation *o = dynamic_cast<Operation*>(pIn.getPreviousObject());
319  if (o) setOperation(o);
320  else throw LogicException("Incorrect object type during read operation");
321  }
322  else if (pAttr.isA(Tags::tag_quantity))
323  setQuantity(pElement.getDouble());
324  else if (pAttr.isA(Tags::tag_priority))
325  setPriority(pElement.getInt());
326  else if (pAttr.isA(Tags::tag_name))
327  setName(pElement.getString());
328  else if (pAttr.isA(Tags::tag_alternate))
329  setAlternate(pElement.getString());
330  else if (pAttr.isA(Tags::tag_search))
331  setSearch(pElement.getString());
332  else if (pAttr.isA(Tags::tag_setup))
333  setSetup(pElement.getString());
334  else if (pAttr.isA(Tags::tag_action))
335  {
336  delete static_cast<Action*>(pIn.getUserArea());
337  pIn.setUserArea(
338  new Action(MetaClass::decodeAction(pElement.getString().c_str()))
339  );
340  }
341  else if (pAttr.isA(Tags::tag_effective_end))
342  setEffectiveEnd(pElement.getDate());
343  else if (pAttr.isA(Tags::tag_effective_start))
344  setEffectiveStart(pElement.getDate());
345  else if (pAttr.isA (Tags::tag_skill))
346  {
347  Skill *s = dynamic_cast<Skill*>(pIn.getPreviousObject());
348  if (s) setSkill(s);
349  else throw LogicException("Incorrect object type during read operation");
350  }
351  else if (pIn.isObjectEnd())
352  {
353  // The load data is now all read in. See if it makes sense now...
354  Action a = pIn.getUserArea() ?
355  *static_cast<Action*>(pIn.getUserArea()) :
356  ADD_CHANGE;
357  delete static_cast<Action*>(pIn.getUserArea());
358  try { validate(a); }
359  catch (...)
360  {
361  delete this;
362  throw;
363  }
364  }
365 }
366 
367 
369 {
370  if (attr.isA(Tags::tag_resource))
371  return PythonObject(getResource());
372  if (attr.isA(Tags::tag_operation))
373  return PythonObject(getOperation());
374  if (attr.isA(Tags::tag_quantity))
375  return PythonObject(getQuantity());
376  if (attr.isA(Tags::tag_priority))
377  return PythonObject(getPriority());
378  if (attr.isA(Tags::tag_effective_end))
379  return PythonObject(getEffective().getEnd());
380  if (attr.isA(Tags::tag_effective_start))
381  return PythonObject(getEffective().getStart());
382  if (attr.isA(Tags::tag_name))
383  return PythonObject(getName());
384  if (attr.isA(Tags::tag_hidden))
385  return PythonObject(getHidden());
386  if (attr.isA(Tags::tag_alternate))
387  return PythonObject(getAlternate());
388  if (attr.isA(Tags::tag_search))
389  {
390  ostringstream ch;
391  ch << getSearch();
392  return PythonObject(ch.str());
393  }
394  if (attr.isA(Tags::tag_setup))
395  return PythonObject(getSetup());
396  if (attr.isA(Tags::tag_skill))
397  return PythonObject(getSkill());
398  return NULL;
399 }
400 
401 
402 DECLARE_EXPORT int Load::setattro(const Attribute& attr, const PythonObject& field)
403 {
404  if (attr.isA(Tags::tag_resource))
405  {
406  if (!field.check(Resource::metadata))
407  {
408  PyErr_SetString(PythonDataException, "load resource must be of type resource");
409  return -1;
410  }
411  Resource* y = static_cast<Resource*>(static_cast<PyObject*>(field));
412  setResource(y);
413  }
414  else if (attr.isA(Tags::tag_operation))
415  {
416  if (!field.check(Operation::metadata))
417  {
418  PyErr_SetString(PythonDataException, "load operation must be of type operation");
419  return -1;
420  }
421  Operation* y = static_cast<Operation*>(static_cast<PyObject*>(field));
422  setOperation(y);
423  }
424  else if (attr.isA(Tags::tag_quantity))
425  setQuantity(field.getDouble());
426  else if (attr.isA(Tags::tag_priority))
427  setPriority(field.getInt());
428  else if (attr.isA(Tags::tag_effective_end))
429  setEffectiveEnd(field.getDate());
430  else if (attr.isA(Tags::tag_effective_start))
431  setEffectiveStart(field.getDate());
432  else if (attr.isA(Tags::tag_name))
433  setName(field.getString());
434  else if (attr.isA(Tags::tag_alternate))
435  {
436  if (!field.check(Load::metadata))
437  setAlternate(field.getString());
438  else
439  {
440  Load *y = static_cast<Load*>(static_cast<PyObject*>(field));
441  setAlternate(y);
442  }
443  }
444  else if (attr.isA(Tags::tag_search))
445  setSearch(field.getString());
446  else if (attr.isA(Tags::tag_setup))
447  setSetup(field.getString());
448  else if (attr.isA(Tags::tag_skill))
449  {
450  if (!field.check(Skill::metadata))
451  {
452  PyErr_SetString(PythonDataException, "load skill must be of type skill");
453  return -1;
454  }
455  Skill* y = static_cast<Skill*>(static_cast<PyObject*>(field));
456  setSkill(y);
457  }
458  else
459  return -1;
460  return 0;
461 }
462 
463 
464 /** @todo this method implementation is not generic enough and not extendible by subclasses. */
465 PyObject* Load::create(PyTypeObject* pytype, PyObject* args, PyObject* kwds)
466 {
467  try
468  {
469  // Pick up the operation
470  PyObject* oper = PyDict_GetItemString(kwds,"operation");
471  if (!PyObject_TypeCheck(oper, Operation::metadata->pythonClass))
472  throw DataException("load operation must be of type operation");
473 
474  // Pick up the resource
475  PyObject* res = PyDict_GetItemString(kwds,"resource");
476  if (!PyObject_TypeCheck(res, Resource::metadata->pythonClass))
477  throw DataException("load resource must be of type resource");
478 
479  // Pick up the quantity
480  PyObject* q1 = PyDict_GetItemString(kwds,"quantity");
481  double q2 = q1 ? PythonObject(q1).getDouble() : 1.0;
482 
483  // Pick up the effective dates
484  DateRange eff;
485  PyObject* eff_start = PyDict_GetItemString(kwds,"effective_start");
486  if (eff_start)
487  {
488  PythonObject d(eff_start);
489  eff.setStart(d.getDate());
490  }
491  PyObject* eff_end = PyDict_GetItemString(kwds,"effective_end");
492  if (eff_end)
493  {
494  PythonObject d(eff_end);
495  eff.setEnd(d.getDate());
496  }
497 
498  // Create the load
499  Load *l = new Load(
500  static_cast<Operation*>(oper),
501  static_cast<Resource*>(res),
502  q2, eff
503  );
504 
505  // Return the object
506  Py_INCREF(l);
507  return static_cast<PyObject*>(l);
508  }
509  catch (...)
510  {
511  PythonType::evalException();
512  return NULL;
513  }
514 }
515 
516 
518 {
519  // Initialize the type
521  x.setName("loadIterator");
522  x.setDoc("frePPLe iterator for loads");
523  x.supportiter();
524  return x.typeReady();
525 }
526 
527 
528 PyObject* LoadIterator::iternext()
529 {
530  PyObject* result;
531  if (res)
532  {
533  // Iterate over loads on a resource
534  if (ir == res->getLoads().end()) return NULL;
535  result = const_cast<Load*>(&*ir);
536  ++ir;
537  }
538  else
539  {
540  // Iterate over loads on an operation
541  if (io == oper->getLoads().end()) return NULL;
542  result = const_cast<Load*>(&*io);
543  ++io;
544  }
545  Py_INCREF(result);
546  return result;
547 }
548 
549 } // end namespace