3737import java .io .IOException ;
3838import java .util .Arrays ;
3939import java .util .Collection ;
40+ import java .util .LinkedHashMap ;
4041import java .util .LinkedList ;
4142import java .util .List ;
4243import java .util .Map ;
@@ -188,35 +189,87 @@ static int widestJumpTableBand(Collection<Integer> numbers) {
188189 */
189190 private void addTypeSwitch (ClassWriter file , String methodName ,
190191 List <String []> types , String comment ) throws IOException {
192+ TypeSplit split = splitTypes (types );
193+
194+ // Splitting the types over two methods only pays off when there is a
195+ // jump table to keep and something that would otherwise stretch it.
196+ if (!split .isSplit ()) {
197+ addSwitchMethod (file , methodName , comment , split .all (), null );
198+ return ;
199+ }
200+
201+ addSwitchMethod (file , methodName , comment , split .inBand , outOfBandNameOf (methodName ));
202+ addOutOfBandMethod (file , methodName , split );
203+ }
204+
205+ /**
206+ * The types of one switch, split into the ones a jump table can hold and
207+ * the ones whose numbers lie too far out for it.
208+ */
209+ private static final class TypeSplit {
210+
211+ private final Map <Integer , String > inBand = new TreeMap <>();
212+
213+ private final Map <Integer , String > outOfBand = new TreeMap <>();
214+
215+ /**
216+ * @return True if the types have to be reached by two switches.
217+ */
218+ boolean isSplit () {
219+ return !inBand .isEmpty () && !outOfBand .isEmpty ();
220+ }
221+
222+ /**
223+ * @return Every type, whether or not the jump table can hold it.
224+ */
225+ Map <Integer , String > all () {
226+ Map <Integer , String > every = new TreeMap <>(inBand );
227+ every .putAll (outOfBand );
228+ return every ;
229+ }
230+ }
231+
232+ /**
233+ * Splits the types into the ones the widest jump table can hold and the
234+ * ones that lie past it.
235+ *
236+ * @param types The types, as pairs of type number and the expression that
237+ * creates one.
238+ * @return The split.
239+ */
240+ private static TypeSplit splitTypes (List <String []> types ) {
191241 Map <Integer , String > byNumber = new TreeMap <>();
192242
193243 for (String [] type : types ) {
194244 byNumber .put (Integer .valueOf (type [0 ]), type [1 ]);
195245 }
196246
197247 int band = widestJumpTableBand (byNumber .keySet ());
198- Map <Integer , String > inBand = new TreeMap <>();
199- Map <Integer , String > outOfBand = new TreeMap <>();
248+ TypeSplit split = new TypeSplit ();
200249
201250 for (Map .Entry <Integer , String > entry : byNumber .entrySet ()) {
202251 boolean fits = Math .abs (entry .getKey ()) <= band ;
203- (fits ? inBand : outOfBand ).put (entry .getKey (), entry .getValue ());
252+ (fits ? split . inBand : split . outOfBand ).put (entry .getKey (), entry .getValue ());
204253 }
205254
206- // Splitting the types over two methods only pays off when there is a
207- // jump table to keep and something that would otherwise stretch it.
208- if (inBand .isEmpty () || outOfBand .isEmpty ()) {
209- addSwitchMethod (file , methodName , comment , byNumber , null );
210- return ;
211- }
255+ return split ;
256+ }
212257
213- String outside = methodName + "OutOfBand" ;
214- addSwitchMethod (file , methodName , comment , inBand , outside );
215- addSwitchMethod (file , outside , "Creates an Element whose type number lies too"
216- + " far out to be held in the jump table of " + methodName + "()."
217- + " This says nothing about how often the type is asked for: the"
218- + " numbers of an Area are not handed out in the order of use." ,
219- outOfBand , null );
258+ private static String outOfBandNameOf (String methodName ) {
259+ return methodName + "OutOfBand" ;
260+ }
261+
262+ /**
263+ * Writes the method that answers for the types the jump table could not
264+ * hold.
265+ */
266+ private void addOutOfBandMethod (ClassWriter file , String methodName ,
267+ TypeSplit split ) throws IOException {
268+ addSwitchMethod (file , outOfBandNameOf (methodName ),
269+ "Creates an Element whose type number lies too far out to be held"
270+ + " in the jump table that is asked first. This says nothing about"
271+ + " how often the type is asked for: the numbers of an Area are not"
272+ + " handed out in the order of use." , split .outOfBand , null );
220273 }
221274
222275 /**
@@ -290,11 +343,26 @@ private void addSwitchMethod(ClassWriter file, String methodName, String comment
290343 MethodWriter method = file .addMethodOpenStatement (false , true , StdStrings .PRIVATE ,
291344 rtype , methodName , Arrays .asList (arg ), null , comment , null , null , false );
292345
346+ addSwitchBody (method , types , fallback );
347+ method .addMethodCloseStatement ();
348+ }
349+
350+ /**
351+ * Writes the body that answers a type number, into a method that is already
352+ * open. The body is one switch, or a switch for each side of zero where the
353+ * numbers lie too far out to be held together.
354+ *
355+ * @param method The method to write the body to.
356+ * @param types The types the body answers for, by type number.
357+ * @param fallback The method the body falls back on for a type number it
358+ * does not answer for, or null to answer with nothing.
359+ */
360+ private void addSwitchBody (MethodWriter method , Map <Integer , String > types ,
361+ String fallback ) throws IOException {
293362 String missing = (fallback == null ) ? "null" : fallback + "(typeNumber)" ;
294363
295364 if (types .isEmpty ()) {
296365 method .addLine ("return " + missing + ";" );
297- method .addMethodCloseStatement ();
298366 return ;
299367 }
300368
@@ -314,12 +382,10 @@ private void addSwitchMethod(ClassWriter file, String methodName, String comment
314382 method .addLine ("}" );
315383 method .addLine ("" );
316384 addSwitchLines (method , negatives , missing , "" );
317- method .addMethodCloseStatement ();
318385 return ;
319386 }
320387
321388 addSwitchLines (method , types , missing , "" );
322- method .addMethodCloseStatement ();
323389 }
324390
325391 /**
@@ -383,18 +449,46 @@ public void createAreaElementFactoryClass(File areaFolder, AreaType area) throws
383449 CompositeField argType = generator .createCompositeElementsDetails (file , false , "typeNumber" ,
384450 TypeUtils .createTypeReference (null , null , "int" , false ), false , false , null );
385451
452+ // The types of each service, gathered up front: an Area whose services
453+ // declare none of their own is reached without a switch over them.
454+ Map <ServiceType , List <String []>> typesByService = new LinkedHashMap <>();
455+ boolean anyServiceHasTypes = false ;
456+
457+ for (ServiceType service : area .getService ()) {
458+ List <String []> serviceTypes = (service .getDataTypes () == null ) ? new LinkedList <>()
459+ : collectTypes (areaName , service .getName (),
460+ service .getDataTypes ().getCompositeOrEnumeration ());
461+ typesByService .put (service , serviceTypes );
462+ anyServiceHasTypes = anyServiceHasTypes || !serviceTypes .isEmpty ();
463+ }
464+
386465 MethodWriter method = file .addMethodOpenStatementOverride (rtype , "createElement" ,
387466 Arrays .asList (argService , argType ), null , false );
388- method .addLine ("switch (serviceNumber) {" );
389- method .addLine (" case 0: return createAreaElement(typeNumber);" );
467+ TypeSplit areaSplit = splitTypes (areaTypes );
390468
391- for (ServiceType service : area .getService ()) {
392- method .addLine (" case " + service .getNumber () + ": return create"
393- + service .getName () + "Element(typeNumber);" );
469+ if (anyServiceHasTypes ) {
470+ method .addLine ("switch (serviceNumber) {" );
471+ method .addLine (" case 0: return createAreaElement(typeNumber);" );
472+
473+ for (ServiceType service : area .getService ()) {
474+ method .addLine (" case " + service .getNumber () + ": return create"
475+ + service .getName () + "Element(typeNumber);" );
476+ }
477+
478+ method .addLine (" default: return null;" );
479+ method .addLine ("}" );
480+ } else {
481+ // Every type of this Area is declared by the Area itself, so the
482+ // types are answered here rather than through a switch over
483+ // services that would only ever lead back to the one branch.
484+ method .addLine ("if (serviceNumber != 0) {" );
485+ method .addLine (" return null; // This Area declares no types under a service" );
486+ method .addLine ("}" );
487+ method .addLine ("" );
488+ addSwitchBody (method , areaSplit .inBand ,
489+ areaSplit .isSplit () ? outOfBandNameOf ("createAreaElement" ) : null );
394490 }
395491
396- method .addLine (" default: return null;" );
397- method .addLine ("}" );
398492 method .addMethodCloseStatement ();
399493
400494 // The factory says which Area it belongs to, so that registering it
@@ -412,15 +506,19 @@ public void createAreaElementFactoryClass(File areaFolder, AreaType area) throws
412506 areaVersion .addLine ("return " + area .getVersion () + ";" );
413507 areaVersion .addMethodCloseStatement ();
414508
415- addTypeSwitch (file , "createAreaElement" , areaTypes ,
416- "Creates an Element declared by the area itself." );
509+ if (anyServiceHasTypes ) {
510+ addTypeSwitch (file , "createAreaElement" , areaTypes ,
511+ "Creates an Element declared by the area itself." );
417512
418- for (ServiceType service : area .getService ()) {
419- List <String []> serviceTypes = (service .getDataTypes () == null ) ? new LinkedList <>()
420- : collectTypes (areaName , service .getName (),
421- service .getDataTypes ().getCompositeOrEnumeration ());
422- addTypeSwitch (file , "create" + service .getName () + "Element" , serviceTypes ,
423- "Creates an Element declared by the " + service .getName () + " service." );
513+ for (Map .Entry <ServiceType , List <String []>> entry : typesByService .entrySet ()) {
514+ addTypeSwitch (file , "create" + entry .getKey ().getName () + "Element" ,
515+ entry .getValue (), "Creates an Element declared by the "
516+ + entry .getKey ().getName () + " service." );
517+ }
518+ } else if (areaSplit .isSplit ()) {
519+ // createElement() holds the jump table itself, so only the types
520+ // that did not fit in it are left to write out
521+ addOutOfBandMethod (file , "createAreaElement" , areaSplit );
424522 }
425523
426524 file .addClassCloseStatement ();
0 commit comments