Total Complexity | 67 |
Total Lines | 626 |
Duplicated Lines | 16.61 % |
Changes | 3 | ||
Bugs | 0 | Features | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like it.cnr.istc.pst.platinum.ai.framework.microkernel.lang.plan.SolutionPlan often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | package it.cnr.istc.pst.platinum.ai.framework.microkernel.lang.plan; |
||
36 | public class SolutionPlan |
||
37 | { |
||
38 | private long horizion; |
||
39 | private long solvingTime; |
||
40 | private String name; |
||
41 | private SearchSpaceNode solutionNode; |
||
42 | private Set<Timeline> timelines; |
||
43 | private Set<Timeline> observations; |
||
44 | private List<Relation> relations; |
||
45 | private PlanControllabilityType controllability; |
||
46 | private Set<Profile> profiles; |
||
47 | |||
48 | /** |
||
49 | * |
||
50 | * @param name |
||
51 | * @param horizon |
||
52 | */ |
||
53 | public SolutionPlan(String name, long horizon) |
||
54 | { |
||
55 | this.horizion = horizon; |
||
56 | this.name = name; |
||
57 | this.timelines = new HashSet<>(); |
||
58 | this.observations = new HashSet<>(); |
||
59 | this.relations = new ArrayList<>(); |
||
60 | this.profiles = new HashSet<>(); |
||
61 | this.controllability = PlanControllabilityType.UNKNOWN; |
||
62 | } |
||
63 | |||
64 | /** |
||
65 | * |
||
66 | * @param node |
||
67 | */ |
||
68 | public void setSolutionNode(SearchSpaceNode node) { |
||
69 | this.solutionNode = node; |
||
70 | } |
||
71 | |||
72 | /** |
||
73 | * |
||
74 | * @return |
||
75 | */ |
||
76 | public SearchSpaceNode getSolutionNode() { |
||
77 | return this.solutionNode; |
||
78 | } |
||
79 | |||
80 | /** |
||
81 | * |
||
82 | * @return |
||
83 | */ |
||
84 | public String getName() { |
||
85 | return name; |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * |
||
90 | * @return |
||
91 | */ |
||
92 | public long getHorizon() { |
||
93 | return this.horizion; |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * |
||
98 | * @return |
||
99 | */ |
||
100 | View Code Duplication | public double[] getMakespan() |
|
|
|||
101 | { |
||
102 | // set makespan |
||
103 | double[] mk = new double[] { |
||
104 | Double.MIN_VALUE + 1, |
||
105 | Double.MAX_VALUE - 1 |
||
106 | }; |
||
107 | |||
108 | // check timelines |
||
109 | for (Timeline tl : this.timelines) |
||
110 | { |
||
111 | // check primitive components only |
||
112 | if (tl.getComponent().getType().equals(DomainComponentType.SV_PRIMITIVE)) |
||
113 | { |
||
114 | // check tokens |
||
115 | if (!tl.getTokens().isEmpty()) { |
||
116 | // get last token of the timeline |
||
117 | Token last = tl.getTokens().get(0); |
||
118 | for (int index = 1; index < tl.getTokens().size(); index++) { |
||
119 | // get token |
||
120 | Token t = tl.getTokens().get(index); |
||
121 | // check last token |
||
122 | if (t.getInterval().getEndTime().getLowerBound() > last.getInterval().getEndTime().getLowerBound()) { |
||
123 | // update last token |
||
124 | last = t; |
||
125 | } |
||
126 | } |
||
127 | |||
128 | // update max end time |
||
129 | mk[0] = Math.max(mk[0], last.getInterval().getEndTime().getLowerBound()); |
||
130 | mk[1] = Math.min(mk[1], last.getInterval().getEndTime().getUpperBound()); |
||
131 | } |
||
132 | } |
||
133 | } |
||
134 | |||
135 | |||
136 | // get makespan |
||
137 | return mk; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * |
||
142 | * @param compName |
||
143 | * @return |
||
144 | */ |
||
145 | View Code Duplication | public double[] getMakespan(String name) |
|
146 | { |
||
147 | // set makespan |
||
148 | double[] mk = new double[] { |
||
149 | 0.0, |
||
150 | 0.0 |
||
151 | }; |
||
152 | |||
153 | // check timelines |
||
154 | for (Timeline tl : this.timelines) |
||
155 | { |
||
156 | // check primitive components only |
||
157 | if (tl.getComponent().getName().equals(name)) |
||
158 | { |
||
159 | // preapre makespan |
||
160 | mk = new double[] { |
||
161 | Double.MIN_VALUE + 1, |
||
162 | Double.MAX_VALUE - 1 |
||
163 | }; |
||
164 | |||
165 | |||
166 | // get last token of the timeline |
||
167 | Token last = tl.getTokens().get(0); |
||
168 | for (int index = 1; index < tl.getTokens().size(); index++) { |
||
169 | // get token |
||
170 | Token t = tl.getTokens().get(index); |
||
171 | // check last token |
||
172 | if (t.getInterval().getEndTime().getLowerBound() > last.getInterval().getEndTime().getLowerBound()) { |
||
173 | // update last token |
||
174 | last = t; |
||
175 | } |
||
176 | } |
||
177 | |||
178 | // update max end time |
||
179 | mk[0] = Math.max(mk[0], last.getInterval().getEndTime().getLowerBound()); |
||
180 | mk[1] = Math.min(mk[1], last.getInterval().getEndTime().getUpperBound()); |
||
181 | |||
182 | // stop search |
||
183 | break; |
||
184 | } |
||
185 | } |
||
186 | |||
187 | |||
188 | // get makespan |
||
189 | return mk; |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * |
||
194 | * @param solvingTime |
||
195 | */ |
||
196 | public void setSolvingTime(long solvingTime) { |
||
197 | this.solvingTime = solvingTime; |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * |
||
202 | * @return |
||
203 | */ |
||
204 | public long getSolvingTime() { |
||
205 | return solvingTime; |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * |
||
210 | * @return |
||
211 | */ |
||
212 | public PlanControllabilityType getPlanControllabilityType() { |
||
213 | return this.controllability; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * |
||
218 | * @param pseudo |
||
219 | */ |
||
220 | public void setControllability(PlanControllabilityType type) { |
||
221 | this.controllability = type; |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * |
||
226 | * @return |
||
227 | */ |
||
228 | public List<Timeline> getTimelines() { |
||
229 | return new ArrayList<>(this.timelines); |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * |
||
234 | * @return |
||
235 | */ |
||
236 | public List<Timeline> getObservations() { |
||
237 | return new ArrayList<>(this.observations); |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * |
||
242 | * @return |
||
243 | */ |
||
244 | public List<Timeline> getAllTimelines() { |
||
245 | List<Timeline> list = new ArrayList<>(); |
||
246 | list.addAll(this.timelines); |
||
247 | list.addAll(this.observations); |
||
248 | return list; |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * |
||
253 | * @param component |
||
254 | */ |
||
255 | public void add(DomainComponent component) |
||
256 | { |
||
257 | // check component type |
||
258 | switch (component.getType()) |
||
259 | { |
||
260 | case SV_FUNCTIONAL : |
||
261 | case SV_PRIMITIVE : { |
||
262 | // get the state variable |
||
263 | StateVariable sv = (StateVariable) component; |
||
264 | // get the timeline |
||
265 | Timeline tl = new Timeline(sv); |
||
266 | // add to timeline |
||
267 | this.timelines.add(tl); |
||
268 | } |
||
269 | break; |
||
270 | |||
271 | case SV_EXTERNAL : { |
||
272 | // get the state variable |
||
273 | StateVariable sv = (StateVariable) component; |
||
274 | // get the timeline |
||
275 | Timeline tl = new Timeline(sv); |
||
276 | // add to observations |
||
277 | this.observations.add(tl); |
||
278 | } |
||
279 | break; |
||
280 | |||
281 | case RESOURCE_DISCRETE : |
||
282 | case RESOURCE_RESERVOIR : { |
||
283 | // get component as a resource |
||
284 | Resource res = (Resource) component; |
||
285 | // get profile |
||
286 | Profile profile = new Profile(res); |
||
287 | // add profile |
||
288 | this.profiles.add(profile); |
||
289 | } |
||
290 | break; |
||
291 | |||
292 | case PLAN_DATABASE : { |
||
293 | // ignore this type of components |
||
294 | } |
||
295 | break; |
||
296 | |||
297 | |||
298 | default : { |
||
299 | throw new RuntimeException("Unknown component type " + component.getType() + "\n"); |
||
300 | } |
||
301 | } |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * |
||
306 | * @return |
||
307 | */ |
||
308 | public List<Relation> getRelations() { |
||
309 | return relations; |
||
310 | } |
||
311 | |||
312 | /** |
||
313 | * |
||
314 | * @param rel |
||
315 | */ |
||
316 | public void add(Relation rel) { |
||
317 | this.relations.add(rel); |
||
318 | } |
||
319 | |||
320 | /** |
||
321 | * |
||
322 | * @param plan |
||
323 | * @return |
||
324 | */ |
||
325 | public PlanProtocolDescriptor export() { |
||
326 | // generate protocol plan descriptor |
||
327 | PlanProtocolDescriptor exported = this.generatePlanDescriptor(); |
||
328 | // get exported plan |
||
329 | return exported; |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * |
||
334 | * @return |
||
335 | */ |
||
336 | protected PlanProtocolDescriptor generatePlanDescriptor() |
||
337 | { |
||
338 | // get language factory |
||
339 | ProtocolLanguageFactory factory = new ProtocolLanguageFactory(this.horizion); |
||
340 | |||
341 | // create plan descriptor |
||
342 | PlanProtocolDescriptor plan = factory.createPlanDescriptor(this.name, 0, this.horizion); |
||
343 | // create an index |
||
344 | Map<Token, TokenProtocolDescriptor> index = new HashMap<>(); |
||
345 | // create timeline descriptors |
||
346 | for (Timeline tl : this.timelines) |
||
347 | { |
||
348 | // get the state variable related to the timeline |
||
349 | StateVariable comp = tl.getComponent(); |
||
350 | // initialize descriptor |
||
351 | TimelineProtocolDescriptor timelineDescriptor = factory.createTimelineDescriptor( |
||
352 | comp.getName(), |
||
353 | tl.getName(), |
||
354 | tl.isObservation()); |
||
355 | |||
356 | // get tokens of the timeline |
||
357 | for (Token token : tl.getTokens()) |
||
358 | { |
||
359 | // prepare the array of parameter names, values, and types |
||
360 | String[] paramNames = new String[token.getPredicate().getParameters().length]; |
||
361 | ParameterTypeDescriptor[] paramTypes = new ParameterTypeDescriptor[token.getPredicate().getParameters().length]; |
||
362 | long[][] paramBounds = new long[token.getPredicate().getParameters().length][]; |
||
363 | String[][] paramValues = new String[token.getPredicate().getParameters().length][]; |
||
364 | for (int i = 0; i < token.getPredicate().getParameters().length; i++) |
||
365 | { |
||
366 | // get parameter |
||
367 | Parameter<?> param = token.getPredicate().getParameterByIndex(i); |
||
368 | // set parameter name |
||
369 | paramNames[i] = param.getLabel(); |
||
370 | |||
371 | // check parameter type |
||
372 | if (param.getType().equals(ParameterType.NUMERIC_PARAMETER_TYPE)) |
||
373 | { |
||
374 | // get numeric parameter |
||
375 | NumericParameter numPar = (NumericParameter) param; |
||
376 | // set lower bound and upper bound |
||
377 | paramBounds[i] = new long[] { |
||
378 | numPar.getLowerBound(), |
||
379 | numPar.getUpperBound() |
||
380 | }; |
||
381 | // set default value to parameter values |
||
382 | paramValues[i] = new String[] {}; |
||
383 | // set parameter type |
||
384 | paramTypes[i] = ParameterTypeDescriptor.NUMERIC; |
||
385 | } |
||
386 | else if (param.getType().equals(ParameterType.ENUMERATION_PARAMETER_TYPE)) |
||
387 | { |
||
388 | // enumeration parameter |
||
389 | EnumerationParameter enuPar = (EnumerationParameter) param; |
||
390 | // one single value is expected |
||
391 | paramValues[i] = new String[] { |
||
392 | enuPar.getValues()[0] |
||
393 | }; |
||
394 | // set default value to parameter bounds |
||
395 | paramBounds[i] = new long[] {}; |
||
396 | // set parameter type |
||
397 | paramTypes[i] = ParameterTypeDescriptor.ENUMERATION; |
||
398 | } |
||
399 | else { |
||
400 | throw new RuntimeException("Unknown parameter type:\n- type: " + param.getType() + "\n"); |
||
401 | } |
||
402 | } |
||
403 | |||
404 | // create token descriptor |
||
405 | TokenProtocolDescriptor tokenDescriptor = factory.createTokenDescriptor( |
||
406 | timelineDescriptor, |
||
407 | token.getPredicate().getValue().getLabel(), |
||
408 | new long [] { |
||
409 | token.getInterval().getStartTime().getLowerBound(), |
||
410 | token.getInterval().getStartTime().getUpperBound() |
||
411 | }, |
||
412 | new long[] { |
||
413 | token.getInterval().getEndTime().getLowerBound(), |
||
414 | token.getInterval().getEndTime().getUpperBound() |
||
415 | }, |
||
416 | new long[] { |
||
417 | token.getInterval().getDurationLowerBound(), |
||
418 | token.getInterval().getDurationUpperBound() |
||
419 | }, |
||
420 | paramNames, paramTypes, paramBounds, paramValues, token.getStartExecutionState()); |
||
421 | |||
422 | // update index |
||
423 | index.put(token, tokenDescriptor); |
||
424 | } |
||
425 | |||
426 | // add timeline to plan |
||
427 | plan.addTimeline(timelineDescriptor); |
||
428 | } |
||
429 | |||
430 | // create timeline descriptors |
||
431 | for (Timeline tl : this.observations) |
||
432 | { |
||
433 | // get the state variable related to the timeline |
||
434 | StateVariable comp = tl.getComponent(); |
||
435 | // initialize descriptor |
||
436 | TimelineProtocolDescriptor timelineDescriptor = factory.createTimelineDescriptor( |
||
437 | comp.getName(), |
||
438 | tl.getName(), |
||
439 | tl.isObservation()); |
||
440 | |||
441 | // get tokens of the timeline |
||
442 | for (Token token : tl.getTokens()) |
||
443 | { |
||
444 | // prepare the array of parameter names, values, and types |
||
445 | String[] paramNames = new String[token.getPredicate().getParameters().length]; |
||
446 | ParameterTypeDescriptor[] paramTypes = new ParameterTypeDescriptor[token.getPredicate().getParameters().length]; |
||
447 | long[][] paramBounds = new long[token.getPredicate().getParameters().length][]; |
||
448 | String[][] paramValues = new String[token.getPredicate().getParameters().length][]; |
||
449 | for (int i = 0; i < token.getPredicate().getParameters().length; i++) |
||
450 | { |
||
451 | // get parameter |
||
452 | Parameter<?> param = token.getPredicate().getParameterByIndex(i); |
||
453 | // check parameter type |
||
454 | if (param.getType().equals(ParameterType.NUMERIC_PARAMETER_TYPE)) { |
||
455 | // get numeric parameter |
||
456 | NumericParameter numPar = (NumericParameter) param; |
||
457 | // set lower bound and upper bound |
||
458 | paramBounds[i] = new long[] { |
||
459 | numPar.getLowerBound(), |
||
460 | numPar.getUpperBound() |
||
461 | }; |
||
462 | // set default value to parameter values |
||
463 | paramValues[i] = new String[] {}; |
||
464 | } |
||
465 | else if (param.getType().equals(ParameterType.ENUMERATION_PARAMETER_TYPE)) { |
||
466 | // enumeration parameter |
||
467 | EnumerationParameter enuPar = (EnumerationParameter) param; |
||
468 | // one single value is expected |
||
469 | paramValues[i] = new String[] { |
||
470 | enuPar.getValues().toString() |
||
471 | }; |
||
472 | // set default value to parameter bounds |
||
473 | paramBounds[i] = new long[] {}; |
||
474 | } |
||
475 | else { |
||
476 | throw new RuntimeException("Unknown parameter type:\n- type: " + param.getType() + "\n"); |
||
477 | } |
||
478 | } |
||
479 | |||
480 | // create token descriptor |
||
481 | TokenProtocolDescriptor tokenDescriptor = factory.createTokenDescriptor( |
||
482 | timelineDescriptor, |
||
483 | token.getPredicate().getValue().getLabel(), |
||
484 | new long [] { |
||
485 | token.getInterval().getStartTime().getLowerBound(), |
||
486 | token.getInterval().getStartTime().getUpperBound() |
||
487 | }, |
||
488 | new long[] { |
||
489 | token.getInterval().getEndTime().getLowerBound(), |
||
490 | token.getInterval().getEndTime().getUpperBound() |
||
491 | }, |
||
492 | new long[] { |
||
493 | token.getInterval().getDurationLowerBound(), |
||
494 | token.getInterval().getDurationUpperBound() |
||
495 | }, |
||
496 | paramNames, paramTypes, paramBounds, paramValues, token.getStartExecutionState()); |
||
497 | |||
498 | // update index |
||
499 | index.put(token, tokenDescriptor); |
||
500 | } |
||
501 | |||
502 | // add timeline to plan |
||
503 | plan.addTimeline(timelineDescriptor); |
||
504 | } |
||
505 | |||
506 | // create relation descriptors |
||
507 | for (Relation relation : this.relations) |
||
508 | { |
||
509 | // export temporal relations only |
||
510 | if (relation.getCategory().equals(ConstraintCategory.TEMPORAL_CONSTRAINT)) |
||
511 | { |
||
512 | // consider only relations between values of state variables |
||
513 | if (relation.getReference().getComponent() instanceof StateVariable && |
||
514 | relation.getTarget().getComponent() instanceof StateVariable) |
||
515 | { |
||
516 | // get temporal relation |
||
517 | TemporalRelation trel = (TemporalRelation) relation; |
||
518 | // create relation description |
||
519 | RelationProtocolDescriptor relDescriptor = factory.createRelationDescriptor( |
||
520 | relation.getType().name().toUpperCase(), |
||
521 | index.get(relation.getReference().getToken()), |
||
522 | index.get(relation.getTarget().getToken())); |
||
523 | |||
524 | // set bounds |
||
525 | relDescriptor.setBounds(trel.getBounds()); |
||
526 | // add relation descriptor to plan |
||
527 | plan.addRelation(relDescriptor); |
||
528 | } |
||
529 | } |
||
530 | } |
||
531 | |||
532 | // return plan descriptor |
||
533 | return plan; |
||
534 | } |
||
535 | |||
536 | /** |
||
537 | * |
||
538 | */ |
||
539 | @Override |
||
540 | public int hashCode() { |
||
541 | final int prime = 31; |
||
542 | int result = 1; |
||
543 | result = prime * result + ((name == null) ? 0 : name.hashCode()); |
||
544 | return result; |
||
545 | } |
||
546 | |||
547 | /** |
||
548 | * |
||
549 | */ |
||
550 | @Override |
||
551 | public boolean equals(Object obj) { |
||
552 | if (this == obj) |
||
553 | return true; |
||
554 | if (obj == null) |
||
555 | return false; |
||
556 | if (getClass() != obj.getClass()) |
||
557 | return false; |
||
558 | SolutionPlan other = (SolutionPlan) obj; |
||
559 | if (name == null) { |
||
560 | if (other.name != null) |
||
561 | return false; |
||
562 | } else if (!name.equals(other.name)) |
||
563 | return false; |
||
564 | return true; |
||
565 | } |
||
566 | |||
567 | /** |
||
568 | * |
||
569 | */ |
||
570 | @Override |
||
571 | public String toString() |
||
572 | { |
||
573 | // get plan makespan |
||
574 | double[] mk = this.getMakespan(); |
||
575 | // initialize solution plan description |
||
576 | String description = "{\n" |
||
577 | + "\t\"horizon\": " + this.horizion + ",\n" |
||
578 | + "\t\"controllability\": \"" + this.controllability.toString().toLowerCase() + "\",\n" |
||
579 | + "\t\"makespan\": [" + mk[0] + ", " + mk[1] + "],\n" |
||
580 | + "\t\"solving_time\": " + solvingTime + ",\n"; |
||
581 | |||
582 | // check domain specific metric if any |
||
583 | if (solutionNode.getDomainSpecificMetric() != null) { |
||
584 | description += "\n"; |
||
585 | description += "\t\"metric\": " + solutionNode.getDomainSpecificMetric().toString() + ",\n"; |
||
586 | description += "\n"; |
||
587 | } |
||
588 | |||
589 | // start description of timelines |
||
590 | description += "\t\"timelines\": [\n"; |
||
591 | View Code Duplication | for (Timeline tl : this.timelines) |
|
592 | { |
||
593 | description += "\t\t{\n" |
||
594 | + "\t\t\t\"name\": \"" + tl.getComponent().getName() + "\",\n" |
||
595 | + "\t\t\t\"tokens\": [\n"; |
||
596 | // get tokens |
||
597 | for (Token token : tl.getTokens()) { |
||
598 | description += "\t\t\t\t" + token + ",\n"; |
||
599 | } |
||
600 | description += "\t\t\t]\n" |
||
601 | + "\t\t},\n"; |
||
602 | } |
||
603 | |||
604 | // end description of timelines |
||
605 | description += "\t],\n\n"; |
||
606 | |||
607 | // print profiles |
||
608 | description += "\t\"profiles\": [\n"; |
||
609 | for (Profile pro : this.profiles) |
||
610 | { |
||
611 | // open description |
||
612 | description += "\t\t{\n" |
||
613 | + "\t\t\t\"name\": \"" + pro.getName() + "\",\n" |
||
614 | + "\t\t\t\"initial-level\": " + pro.getInitCapacity() + ",\n" |
||
615 | + "\t\t\t\"min\": " + pro.getMinCapacity() + ",\n" |
||
616 | + "\t\t\t\"max\": " + pro.getMaxCapacity() + ",\n" |
||
617 | + "\t\t\t\"events\": [\n"; |
||
618 | |||
619 | // get events |
||
620 | for (ResourceEvent<?> event : pro.getResourceEvents()) { |
||
621 | description += "\t\t\t\t[\"update\": " + event.getAmount() + " " + event.getEvent() + "],\n"; |
||
622 | } |
||
623 | |||
624 | // close description |
||
625 | description += "\t\t\t]\n" |
||
626 | + "\t\t},\n"; |
||
627 | } |
||
628 | |||
629 | // description of profiles |
||
630 | description += "\t],\n\n"; |
||
631 | |||
632 | // start description of observations |
||
633 | description += "\t\"observations\": [\n"; |
||
634 | View Code Duplication | for (Timeline tl : this.observations) { |
|
635 | description += "\t\t{\n" |
||
636 | + "\t\t\tname: \"" + tl.getComponent().getName() + "\",\n" |
||
637 | + "\t\t\ttokens: [\n"; |
||
638 | // get tokens |
||
639 | for (Token token : tl.getTokens()) { |
||
640 | description += "\t\t\t\t" + token + ",\n"; |
||
641 | } |
||
642 | description += "\t\t\t]\n" |
||
643 | + "\t\t},\n"; |
||
644 | } |
||
645 | |||
646 | // end description of observations |
||
647 | description += "\t],\n\n"; |
||
648 | |||
649 | // start description of relations |
||
650 | description += "\t\"relations\": [\n"; |
||
651 | for (Relation rel : this.relations) { |
||
652 | description += "\t\t" + rel + ",\n"; |
||
653 | } |
||
654 | |||
655 | // end description of relations |
||
656 | description += "\t]\n\n"; |
||
657 | |||
658 | // close plan description |
||
659 | description += "}\n\n"; |
||
660 | // get description |
||
661 | return description; |
||
662 | } |
||
666 |