Skip to contentMethod: getBlattZahl()
      1: package produktionsauftrag;
2: 
3: import auftraege.ProduktionsAuftrag;
4: import auftraege.ServiceLevelAgreement;
5: import auftraege.UnpassendeKlasseException;
6: import auftraege.auftragsBildungsParameter.BeilagenArten;
7: import auftraege.auftragsBildungsParameter.FarbDruckTypMischbar;
8: import auftraege.auftragsBildungsParameter.MaxBeilagenarten;
9: import auftraege.auftragsBildungsParameter.MaxKundenauftraege;
10: import auftraege.auftragsBildungsParameter.SimplexDuplexMischbar;
11: import auftraege.auftragsBildungsParameter.abstraction.DokumentenklassenVariable;
12: import auftraege.auftragsBildungsParameter.abstraction.DokumentenklassenVariablenVisitor;
13: import auftraege.auftragsBildungsParameter.abstraction.ProduktionsauftragsParameter;
14: import auftraege.auftragsBildungsParameter.dokumentenKlassenVariablen.Beilagenart;
15: import auftraege.auftragsBildungsParameter.dokumentenKlassenVariablen.BlattAnzahl;
16: import auftraege.auftragsBildungsParameter.dokumentenKlassenVariablen.Id;
17: import auftraege.auftragsBildungsParameter.dokumentenKlassenVariablen.Kommentar;
18: import auftraege.auftragsBildungsParameter.dokumentenKlassenVariablen.ProzessModell;
19: import auftraege.auftragsBildungsParameter.dokumentenKlassenVariablen.SendungsAnzahl;
20: import auftraege.auftragsBildungsParameter.dokumentenKlassenVariablen.UnbekannteVariable;
21: import auftraege.auftragsBildungsParameter.dokumentenKlassenVariablen.UnbekannteVariablen;
22: import auftraege.versand.Kunde;
23: import maschine.faehigkeit.DruckTyp;
24: import maschine.faehigkeit.FarbDruckTyp;
25: import material.auspraegungen.Papierformat;
26: import material.kuvert.KuvertFormat;
27: import util.ClassObjectMap;
28: import util.exceptions.AlreadyExistsException;
29: import util.exceptions.SollteNichtPassierenException;
30: 
31: import java.util.ArrayList;
32: import java.util.Comparator;
33: import java.util.HashMap;
34: import java.util.List;
35: import java.util.Map;
36: import java.util.Map.Entry;
37: import java.util.Optional;
38: import java.util.Set;
39: import java.util.stream.Collectors;
40: import java.util.stream.Stream;
41: 
42: /**
43:  * Hilfsklasse zum bauen von Produktionsaufträgen.
44:  */
45: class ProduktionsauftragsBuilder {
46: 
47:         private final Map<DokumentenklasseBuilder, Double> dokumentenklassen;
48:         private final ClassObjectMap<DokumentenklassenVariable> kompatibilitaetsParameter;
49:         private final ClassObjectMap<ProduktionsauftragsParameter> produktionsauftragsParameter;
50: 
51:         /**
52:          * erstellt einen neuen leeren {@link ProduktionsauftragsBuilder}.
53:          *
54:          * @param produktionsauftragsParameter {@link ClassObjectMap} mit {@link ProduktionsauftragsParameter}n, die bei der Bildung von Produktionsaufträgen berücksichtigt
55:          *                                     werden sollen.
56:          */
57:         ProduktionsauftragsBuilder(final ClassObjectMap<ProduktionsauftragsParameter> produktionsauftragsParameter) {
58:                 this.produktionsauftragsParameter = produktionsauftragsParameter;
59:                 this.dokumentenklassen = new HashMap<>();
60:                 this.kompatibilitaetsParameter = new ClassObjectMap<>();
61:         }
62: 
63:         /**
64:          * Baut einen neuen {@link ProduktionsAuftrag} aus den Klassen und Parametern des {@link ProduktionsauftragsBuilder}s.
65:          *
66:          * @return ProduktionsAuftrag
67:          */
68:         ProduktionsAuftrag baue() {
69:                 return new ProduktionsAuftrag(
70:                                 this.dokumentenklassen.entrySet().stream()
71:                                                 .map((final Entry<DokumentenklasseBuilder, Double> k) -> k.getKey().baue(k.getValue()))
72:                                                 .collect(Collectors.toList()));
73:         }
74: 
75:         /**
76:          * Prüft, ob die Dokumentenklasse schon in dem Produktionsauftrag vorhanden ist.
77:          *
78:          * @param klasse ist die zu überprüfende Klasse
79:          * @param faktor der Faktor unter dem das Ergebnis liegen muss
80:          * @return True, wenn die Klasse mit weniger Prozent als Faktor in dem Auftrag vorhanden ist.
81:          */
82:         private boolean hatPlatzfuerKlasse(final DokumentenklasseBuilder klasse, final double faktor) {
83:                 if (!this.dokumentenklassen.containsKey(klasse)) {
84:                         return true;
85:                 }
86:                 return (faktor - (1 - this.dokumentenklassen.get(klasse))) <= 0;
87:         }
88: 
89:         /**
90:          * @return Anzahl der Blätter in dem Produktionsauftrag. Mindestens eins
91:          */
92:         Integer getBlattZahl() {
93:                 return this.dokumentenklassen.entrySet().stream()
94:                                 .mapToInt((final Entry<DokumentenklasseBuilder, Double> k) -> (int) (k.getKey().getAnzahlBlaetter() * k.getValue())).sum();
95:         }
96: 
97:         /**
98:          * @return den frühesten {@link ServiceLevelAgreement} aller {@link auftraege.Dokumentenklasse}n in diesem {@link ProduktionsauftragsBuilder}. Sollte keine dieser
99:          * Dokumentenklassen ein SLA besitzen, so wird ein Maximaler SLA zurückgegeben.
100:          */
101:         public ServiceLevelAgreement getSla() {
102:                 return this.dokumentenklassen.keySet().stream()
103:                                 .min(Comparator.comparing(dokumentenklassenBuilder -> dokumentenklassenBuilder.getSla().getFrist(dokumentenklassenBuilder.getEingangszeitpunkt())))
104:                                 .map(DokumentenklasseBuilder::getSla).orElse(ServiceLevelAgreement.MAX_VALUE);
105:         }
106: 
107:         /**
108:          * Fügt eine Klasse oder einen Teil der Klasse in den Produktionsauftrag ein.
109:          *
110:          * @param klasse     ist die einzufügende Klasse von Dokumenten.
111:          * @param plusFaktor ist der Faktor von 0 bis 1, zu dem die Klasse in den Produktionsauftrag eingefügt werden kann.
112:          * @throws AlreadyExistsException     wenn die Klasse mit mehr als 100 % in dem Auftfrag wäre.
113:          * @throws FaktorOutOfBoundsException wenn Faktor kleiner oder gleich 0.
114:          * @throws UnpassendeKlasseException  wenn die eindeutigen Attribute nicht übereinstimmen.
115:          */
116:         void addDokumentenklasse(final DokumentenklasseBuilder klasse, final double plusFaktor)
117:                         throws AlreadyExistsException, FaktorOutOfBoundsException, UnpassendeKlasseException {
118:                 if (plusFaktor <= 0 || plusFaktor > 1) {
119:                         throw new FaktorOutOfBoundsException();
120:                 }
121:                 if (!this.hatPlatzfuerKlasse(klasse, plusFaktor)) {
122:                         // TODO Art: unidentified; siehe Message der AlreadyExistsException.
123:                         throw new AlreadyExistsException("Hier muss sich noch etwas sinnvolles überlegt werden.");
124:                 }
125:                 if (this.dokumentenklassen.isEmpty()) {
126:                         this.prepareKompatibitlitaetsParameter(klasse);
127:                 } else if (!this.pruefeKompatibilitaet(klasse, true)) {
128:                         throw new UnpassendeKlasseException();
129:                 }
130: 
131:                 Double faktor = this.dokumentenklassen.get(klasse);
132:                 faktor = faktor == null ? plusFaktor : faktor + plusFaktor;
133:                 this.dokumentenklassen.put(klasse, faktor);
134:                 klasse.setBereitsEingeplanteBlaetter(klasse.getBereitsEingeplanteBlaetter() + (int) (klasse.getAnzahlBlaetter() * plusFaktor));
135:         }
136: 
137:         private void prepareKompatibitlitaetsParameter(final DokumentenklasseBuilder klasse) {
138:                 final Set<DokumentenklassenVariable> params = klasse.getVariablen().getBySuperclass(DokumentenklassenVariable.class);
139:                 params.forEach(this.kompatibilitaetsParameter::put);
140:         }
141: 
142:         /**
143:          * Prüft, ob eine Dokumentenklasse in den ProduktionsausftragsBuilder gesetzt werden kann, oder ob es aufgrund enthaltener Dokumentenklassen zu inkompatibilitäten
144:          * führt.
145:          *
146:          * @param klasse DokumentenklassenBuilder, zu welchem geprüft werden soll, ob dieser in den bestehenden Produktionsauftragbuilder hinzugefügt werden kann.
147:          * @return true, wenn noch keine Dokumentenklassen hinzugefügt wurden oder alle enthaltenen Dokumentenklassen nur Variablen besitzen, welche kompatibel zu den
148:          * Variablen des Parameter {@code klasse} sind.
149:          */
150:         boolean pruefeKompatibilitaet(final DokumentenklasseBuilder klasse) {
151:                 return this.pruefeKompatibilitaet(klasse, false);
152:         }
153: 
154:         /**
155:          * Prüft, ob eine Dokumentenklasse in den ProduktionsausftragsBuilder gesetzt werden kann, oder ob es aufgrund enthaltener Dokumentenklassen zu inkompatibilitäten
156:          * führt.
157:          *
158:          * @param klasse                 DokumentenklassenBuilder, zu welchem geprüft werden soll, ob dieser in den bestehenden Produktionsauftragbuilder hinzugefügt werden
159:          *                               kann.
160:          * @param passeKompatibilitaetAn falls dieser Boolean auf <i>true</i> steht, wird davon ausgegangen, dass {@code klasse} eingefügt wird und damit die {@link
161:          *                               ProduktionsauftragsBuilder#kompatibilitaetsParameter} angepasst werden müssen.<br> Dies wird nun automatisiert getan, falls das
162:          *                               Prüfungsergebnis <i>true</i> ist.
163:          * @return true, wenn noch keine Dokumentenklassen hinzugefügt wurden oder alle enthaltenen Dokumentenklassen nur Variablen besitzen, welche kompatibel zu den
164:          * Variablen des Parameter {@code klasse} sind.
165:          */
166:         private boolean pruefeKompatibilitaet(final DokumentenklasseBuilder klasse, final boolean passeKompatibilitaetAn) {
167:                 if (this.kompatibilitaetsParameter.isEmpty()) {
168:                         if (passeKompatibilitaetAn) {
169:                                 this.prepareKompatibitlitaetsParameter(klasse);
170:                         }
171:                         return true;
172:                 } else {
173:                         if (this.dokumentenklassen.size()
174:                                         >= this.produktionsauftragsParameter.get(MaxKundenauftraege.class).map(MaxKundenauftraege::getValue).orElse(Integer.MAX_VALUE)) {
175:                                 return false;
176:                         }
177:                         /*
178:                          * Liste der Kommandos, welche ausgeführt werden, wenn @param passendeKompatibilitaetAn und das Prüfergebnis <i>true</i> sind.
179:                          */
180:                         final List<Runnable> after = new ArrayList<>();
181:                         final Boolean pruefErgebnis = (klasse.getVariable(UnbekannteVariablen.class).isPresent() || this.dokumentenklassen
182:                                         .keySet()
183:                                         .stream()
184:                                         .map(d -> d.getVariable(UnbekannteVariablen.class))
185:                                         .noneMatch(Optional::isPresent))
186:                                         && klasse.getVariablen().valueStream()
187:                                         .map(dokumentenklassenVariable -> dokumentenklassenVariable.accept(new DokumentenklassenVariablenVisitorImpl(after)))
188:                                         .reduce(true, (current, rest) -> current && rest);
189:                         if (pruefErgebnis) {
190:                                 after.forEach(Runnable::run);
191:                         }
192:                         return pruefErgebnis;
193:                 }
194:         }
195: 
196:     /**
197:      * Innere Klasse, welche einen Namen bekommen hat damit Checkstyle nicht meckert.
198:      * // TODO Art: unidentified; Jan oder Jeremy bitte beschreiben, was diese Klasse genau tut und evtl den Namen anpassen.
199:      */
200:     private class DokumentenklassenVariablenVisitorImpl implements DokumentenklassenVariablenVisitor<Boolean> {
201:         private final List<Runnable> after;
202: 
203:         DokumentenklassenVariablenVisitorImpl(final List<Runnable> after) {
204:             this.after = after;
205:         }
206: 
207:         @Override
208:         public Boolean handle(final KuvertFormat kuvertFormat) {
209:             return ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.get(KuvertFormat.class)
210:                     .map(kuvertFormat::equals)
211:                     .orElseGet(() -> {
212:                         this.after.add(() -> ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.put(kuvertFormat));
213:                         return true;
214:                     });
215: 
216:         }
217: 
218:         @Override
219:         public Boolean handle(final BeilagenArten beilagen) {
220:             final Optional<MaxBeilagenarten> maxBeilagenarten = ProduktionsauftragsBuilder.this.produktionsauftragsParameter.get(MaxBeilagenarten.class);
221:             if (!maxBeilagenarten.isPresent()) {
222:                 //Falls keine Begrenzung für MaxBeilagenarten existiert, dann ist auf jeden Fall Platz!
223:                 return true;
224:             } else {
225:                 // ermitteln aller bereits verwendeten Beilagen durch Iteration über sämtliche bereits
226:                 // eingeplanten Dokumentenklassen und Aggregation ihrer BeilagenArten
227:                 final Set<Beilagenart> bereitsEingeplanteBeilagen = ProduktionsauftragsBuilder.this.dokumentenklassen.keySet().stream()
228:                         .map(d -> d.getVariablen().get(BeilagenArten.class))
229:                         .map(beilagenarten -> beilagenarten.orElse(BeilagenArten.EMPTY).getBeilagenarten())
230:                         .flatMap(Set::stream)
231:                         .collect(Collectors.toSet());
232: 
233:                 //Prüfung, ob nach Hinzufügen der hinzukommenden BeilagenArten die Kapazitätsgrenze überschritten wird.
234:                 return Stream.concat(bereitsEingeplanteBeilagen.stream(), beilagen.getBeilagenarten().stream())
235:                         .distinct()
236:                         .count() <= maxBeilagenarten.map(MaxBeilagenarten::getBegrenzung)
237:                         .orElseThrow(SollteNichtPassierenException::new);
238:             }
239:         }
240: 
241:         @Override
242:         public Boolean handle(final ProzessModell prozessModell) {
243:             return ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.get(ProzessModell.class)
244:                     .map(prozessModell::equals)
245:                     .orElseGet(() -> {
246:                         this.after.add(() -> ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.put(prozessModell));
247:                         return true;
248:                     });
249:         }
250: 
251:         @Override
252:         public Boolean handle(final DruckTyp druckTyp) {
253:             return ProduktionsauftragsBuilder.this.produktionsauftragsParameter.get(SimplexDuplexMischbar.class)
254:                     .isPresent() ? true
255:                     : ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.get(DruckTyp.class)
256:                     .map(druckTyp::equals)
257:                     .orElseGet(() -> {
258:                         this.after.add(() -> ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.put(druckTyp));
259:                         return true;
260:                     });
261:         }
262: 
263:         @Override
264:         public Boolean handle(final FarbDruckTyp farbDruckTyp) {
265:             return ProduktionsauftragsBuilder.this.produktionsauftragsParameter.get(FarbDruckTypMischbar.class)
266:                     .isPresent() ? true
267:                     : ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.get(FarbDruckTyp.class)
268:                     .map(farbDruckTyp::equals)
269:                     .orElseGet(() -> {
270:                         this.after.add(() -> ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.put(farbDruckTyp));
271:                         return true;
272:                     });
273:         }
274: 
275:         @Override
276:         public Boolean handle(final ServiceLevelAgreement serviceLevelAgreement) {
277:             return true;
278:         }
279: 
280:         @Override
281:         public Boolean handle(final Papierformat papierformat) {
282:             //TODO Art: Work in Progress; hier muss auch noch auf die Produktionsauftragsparameter geschaut werden, welche aktuell noch nicht implementiert sind.
283:             return ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.get(Papierformat.class)
284:                     .map(papierformat::equals)
285:                     .orElseGet(() -> {
286:                         this.after.add(() -> ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.put(papierformat));
287:                         return true;
288:                     });
289:         }
290: 
291:         @Override
292:         public Boolean handle(final BlattAnzahl blattAnzahl) {
293:             return true;
294:         }
295: 
296:         @Override
297:         public Boolean handle(final Id id) {
298:             return true;
299:         }
300: 
301:         @Override
302:         public Boolean handle(final SendungsAnzahl sendungsAnzahl) {
303:             return true;
304:         }
305: 
306:         @Override
307:         public Boolean handle(final Kunde kunde) {
308:             return true;
309:         }
310: 
311:         @Override
312:         public Boolean handle(final UnbekannteVariable unbekannteVariable) {
313:             //TODO Art: unidentified; eigentlich sollte UnbekannteVariable auch keine DokumentenklassenVariable mehr sein,
314:             //lässt sich aber zzt. nicht ohne großes Refactoring ändern vermutlich
315:             throw new SollteNichtPassierenException("Alle unbekannten Variablen sollten an dieser Stelle bereits herausgefiltert worden sein.");
316:         }
317: 
318:         @Override
319:         public Boolean handle(final UnbekannteVariablen unbekannteVariablen) {
320:             return ProduktionsauftragsBuilder.this.kompatibilitaetsParameter.get(UnbekannteVariablen.class)
321:                     .map(unbekannteVariablen::equals)
322:                             .orElse(false);
323:         }
324: 
325:         @Override
326:         public Boolean handle(final Kommentar kommentar) {
327:             return true;
328:         }
329:     }
330: }