load.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2012 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 != NOHEADER);
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  o->EndObject(tag);
293 }
294 
295 
297 {
298  if (pAttr.isA (Tags::tag_resource))
300  else if (pAttr.isA (Tags::tag_operation))
302  else if (pAttr.isA (Tags::tag_skill))
304 }
305 
306 
307 DECLARE_EXPORT void Load::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
308 {
309  if (pAttr.isA (Tags::tag_resource))
310  {
311  Resource *r = dynamic_cast<Resource*>(pIn.getPreviousObject());
312  if (r) setResource(r);
313  else throw LogicException("Incorrect object type during read operation");
314  }
315  else if (pAttr.isA (Tags::tag_operation))
316  {
317  Operation *o = dynamic_cast<Operation*>(pIn.getPreviousObject());
318  if (o) setOperation(o);
319  else throw LogicException("Incorrect object type during read operation");
320  }
321  else if (pAttr.isA(Tags::tag_quantity))
322  setQuantity(pElement.getDouble());
323  else if (pAttr.isA(Tags::tag_priority))
324  setPriority(pElement.getInt());
325  else if (pAttr.isA(Tags::tag_name))
326  setName(pElement.getString());
327  else if (pAttr.isA(Tags::tag_alternate))
328  setAlternate(pElement.getString());
329  else if (pAttr.isA(Tags::tag_search))
330  setSearch(pElement.getString());
331  else if (pAttr.isA(Tags::tag_setup))
332  setSetup(pElement.getString());
333  else if (pAttr.isA(Tags::tag_action))
334  {
335  delete static_cast<Action*>(pIn.getUserArea());
336  pIn.setUserArea(
337  new Action(MetaClass::decodeAction(pElement.getString().c_str()))
338  );
339  }
340  else if (pAttr.isA(Tags::tag_effective_end))
341  setEffectiveEnd(pElement.getDate());
342  else if (pAttr.isA(Tags::tag_effective_start))
343  setEffectiveStart(pElement.getDate());
344  else if (pAttr.isA (Tags::tag_skill))
345  {
346  Skill *s = dynamic_cast<Skill*>(pIn.getPreviousObject());
347  if (s) setSkill(s);
348  else throw LogicException("Incorrect object type during read operation");
349  }
350  else if (pIn.isObjectEnd())
351  {
352  // The load data is now all read in. See if it makes sense now...
353  Action a = pIn.getUserArea() ?
354  *static_cast<Action*>(pIn.getUserArea()) :
355  ADD_CHANGE;
356  delete static_cast<Action*>(pIn.getUserArea());
357  try { validate(a); }
358  catch (...)
359  {
360  delete this;
361  throw;
362  }
363  }
364 }
365 
366 
368 {
369  if (attr.isA(Tags::tag_resource))
370  return PythonObject(getResource());
371  if (attr.isA(Tags::tag_operation))
372  return PythonObject(getOperation());
373  if (attr.isA(Tags::tag_quantity))
374  return PythonObject(getQuantity());
375  if (attr.isA(Tags::tag_priority))
376  return PythonObject(getPriority());
377  if (attr.isA(Tags::tag_effective_end))
378  return PythonObject(getEffective().getEnd());
379  if (attr.isA(Tags::tag_effective_start))
380  return PythonObject(getEffective().getStart());
381  if (attr.isA(Tags::tag_name))
382  return PythonObject(getName());
383  if (attr.isA(Tags::tag_hidden))
384  return PythonObject(getHidden());
385  if (attr.isA(Tags::tag_alternate))
386  return PythonObject(getAlternate());
387  if (attr.isA(Tags::tag_search))
388  {
389  ostringstream ch;
390  ch << getSearch();
391  return PythonObject(ch.str());
392  }
393  if (attr.isA(Tags::tag_setup))
394  return PythonObject(getSetup());
395  if (attr.isA(Tags::tag_skill))
396  return PythonObject(getSkill());
397  return NULL;
398 }
399 
400 
401 DECLARE_EXPORT int Load::setattro(const Attribute& attr, const PythonObject& field)
402 {
403  if (attr.isA(Tags::tag_resource))
404  {
405  if (!field.check(Resource::metadata))
406  {
407  PyErr_SetString(PythonDataException, "load resource must be of type resource");
408  return -1;
409  }
410  Resource* y = static_cast<Resource*>(static_cast<PyObject*>(field));
411  setResource(y);
412  }
413  else if (attr.isA(Tags::tag_operation))
414  {
415  if (!field.check(Operation::metadata))
416  {
417  PyErr_SetString(PythonDataException, "load operation must be of type operation");
418  return -1;
419  }
420  Operation* y = static_cast<Operation*>(static_cast<PyObject*>(field));
421  setOperation(y);
422  }
423  else if (attr.isA(Tags::tag_quantity))
424  setQuantity(field.getDouble());
425  else if (attr.isA(Tags::tag_priority))
426  setPriority(field.getInt());
427  else if (attr.isA(Tags::tag_effective_end))
428  setEffectiveEnd(field.getDate());
429  else if (attr.isA(Tags::tag_effective_start))
430  setEffectiveStart(field.getDate());
431  else if (attr.isA(Tags::tag_name))
432  setName(field.getString());
433  else if (attr.isA(Tags::tag_alternate))
434  {
435  if (!field.check(Load::metadata))
436  setAlternate(field.getString());
437  else
438  {
439  Load *y = static_cast<Load*>(static_cast<PyObject*>(field));
440  setAlternate(y);
441  }
442  }
443  else if (attr.isA(Tags::tag_search))
444  setSearch(field.getString());
445  else if (attr.isA(Tags::tag_setup))
446  setSetup(field.getString());
447  else if (attr.isA(Tags::tag_skill))
448  {
449  if (!field.check(Skill::metadata))
450  {
451  PyErr_SetString(PythonDataException, "load skill must be of type skill");
452  return -1;
453  }
454  Skill* y = static_cast<Skill*>(static_cast<PyObject*>(field));
455  setSkill(y);
456  }
457  else
458  return -1;
459  return 0;
460 }
461 
462 
463 /** @todo this method implementation is not generic enough and not extendible by subclasses. */
464 PyObject* Load::create(PyTypeObject* pytype, PyObject* args, PyObject* kwds)
465 {
466  try
467  {
468  // Pick up the operation
469  PyObject* oper = PyDict_GetItemString(kwds,"operation");
470  if (!PyObject_TypeCheck(oper, Operation::metadata->pythonClass))
471  throw DataException("load operation must be of type operation");
472 
473  // Pick up the resource
474  PyObject* res = PyDict_GetItemString(kwds,"resource");
475  if (!PyObject_TypeCheck(res, Resource::metadata->pythonClass))
476  throw DataException("load resource must be of type resource");
477 
478  // Pick up the quantity
479  PyObject* q1 = PyDict_GetItemString(kwds,"quantity");
480  double q2 = q1 ? PythonObject(q1).getDouble() : 1.0;
481 
482  // Pick up the effective dates
483  DateRange eff;
484  PyObject* eff_start = PyDict_GetItemString(kwds,"effective_start");
485  if (eff_start)
486  {
487  PythonObject d(eff_start);
488  eff.setStart(d.getDate());
489  }
490  PyObject* eff_end = PyDict_GetItemString(kwds,"effective_end");
491  if (eff_end)
492  {
493  PythonObject d(eff_end);
494  eff.setEnd(d.getDate());
495  }
496 
497  // Create the load
498  Load *l = new Load(
499  static_cast<Operation*>(oper),
500  static_cast<Resource*>(res),
501  q2, eff
502  );
503 
504  // Return the object
505  Py_INCREF(l);
506  return static_cast<PyObject*>(l);
507  }
508  catch (...)
509  {
510  PythonType::evalException();
511  return NULL;
512  }
513 }
514 
515 
517 {
518  // Initialize the type
520  x.setName("loadIterator");
521  x.setDoc("frePPLe iterator for loads");
522  x.supportiter();
523  return x.typeReady();
524 }
525 
526 
527 PyObject* LoadIterator::iternext()
528 {
529  PyObject* result;
530  if (res)
531  {
532  // Iterate over loads on a resource
533  if (ir == res->getLoads().end()) return NULL;
534  result = const_cast<Load*>(&*ir);
535  ++ir;
536  }
537  else
538  {
539  // Iterate over loads on an operation
540  if (io == oper->getLoads().end()) return NULL;
541  result = const_cast<Load*>(&*io);
542  ++io;
543  }
544  Py_INCREF(result);
545  return result;
546 }
547 
548 } // end namespace