Skip to content

Commit c4bb234

Browse files
CesarCoelhoclaude
andcommitted
Answer the types of an Area from createElement() where it declares them all
Most Areas declare every one of their types at Area level, and their services declare none. The switch over the service number then had one branch that led anywhere, and a method for each service that only ever returned nothing. Where no service of an Area declares a type, createElement() now holds the switch over the type numbers itself. The MAL reaches its jump table straight away rather than through a call that HotSpot would not have inlined, the switch over the service numbers is gone from twelve of the seventeen generated factories, and about forty methods that returned nothing are no longer written at all. Areas whose services do declare types are written as before. The factories are registered while the messages of the ones already registered are being decoded, so the list they are held in is now one that can be read through at the same time. Which factory answers when two of them claim a type is written down: the first registered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 7a387db commit c4bb234

3 files changed

Lines changed: 276 additions & 39 deletions

File tree

api-generator/generator-java/src/main/java/esa/mo/tools/stubgen/java/JavaElementFactory.java

Lines changed: 132 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.io.IOException;
3838
import java.util.Arrays;
3939
import java.util.Collection;
40+
import java.util.LinkedHashMap;
4041
import java.util.LinkedList;
4142
import java.util.List;
4243
import 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();

apis/api-area001-v003-mal/src/main/java/org/ccsds/moims/mo/mal/MALElementsRegistry.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package org.ccsds.moims.mo.mal;
2222

2323
import java.util.List;
24+
import java.util.concurrent.CopyOnWriteArrayList;
2425
import java.util.logging.Level;
2526
import java.util.logging.Logger;
2627
import org.ccsds.moims.mo.mal.structures.Element;
@@ -42,15 +43,22 @@ public class MALElementsRegistry {
4243
* says which Area it belongs to, so nothing here can register a factory
4344
* against the wrong number or version.
4445
*/
45-
private final List<AreaElementFactory> AREA_FACTORIES = new java.util.ArrayList<>();
46+
private final List<AreaElementFactory> AREA_FACTORIES = new CopyOnWriteArrayList<>();
4647

4748
/**
4849
* Registers a factory, so that the Elements it creates can be reached
4950
* without every one of their classes having to be loaded first.
5051
*
5152
* More than one factory can be registered for the same Area. Types whose
5253
* numbers the XML schema cannot express are written by hand, and their
53-
* factory is registered alongside the generated one of that Area.
54+
* factory is registered alongside the generated one of that Area. Should
55+
* two of them answer for the same type, the one registered first is the one
56+
* that is asked, so a factory added later cannot take a type away from the
57+
* one that already had it.
58+
*
59+
* Areas are registered while the messages of the ones already registered
60+
* are being decoded, so the list this adds to is one that can be read
61+
* through at the same time.
5462
*
5563
* @param factory The factory that creates the Elements.
5664
*/
@@ -84,9 +92,10 @@ private Element createFromAreaFactory(final long typeId) {
8492
final int serviceNumber = TypeId.serviceNumberOf(typeId);
8593
final int typeNumber = TypeId.typeNumberOf(typeId);
8694

87-
for (int i = 0; i < AREA_FACTORIES.size(); i++) {
88-
AreaElementFactory factory = AREA_FACTORIES.get(i);
89-
95+
// Walked in one go, over the factories that were there when this
96+
// started: a factory registered halfway through must not be seen for
97+
// some of this scan and not for the rest of it.
98+
for (AreaElementFactory factory : AREA_FACTORIES) {
9099
if (factory.getAreaNumber() == areaNumber && factory.getAreaVersion() == areaVersion) {
91100
Element element = factory.createElement(serviceNumber, typeNumber);
92101

0 commit comments

Comments
 (0)