|
| 1 | +/* |
| 2 | + * Copyright © 2015 Integrated Knowledge Management (support@ikm.dev) |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | +package dev.ikm.tinkar.entity.builder; |
| 17 | + |
| 18 | +import dev.ikm.tinkar.common.service.CachingService; |
| 19 | +import dev.ikm.tinkar.common.service.PrimitiveData; |
| 20 | +import dev.ikm.tinkar.coordinate.Calculators; |
| 21 | +import dev.ikm.tinkar.coordinate.language.calculator.LanguageCalculator; |
| 22 | +import dev.ikm.tinkar.coordinate.stamp.calculator.StampCalculator; |
| 23 | +import dev.ikm.tinkar.entity.builder.generator.SectionEmitter; |
| 24 | +import dev.ikm.tinkar.entity.builder.generator.TaxonomySectioner; |
| 25 | +import dev.ikm.tinkar.entity.builder.generator.TaxonomySectioner.Section; |
| 26 | +import dev.ikm.tinkar.entity.builder.generator.TinkarTermReferenceResolver; |
| 27 | +import dev.ikm.tinkar.entity.load.LoadEntitiesFromProtobufFile; |
| 28 | +import dev.ikm.tinkar.terms.TinkarTerm; |
| 29 | + |
| 30 | +import java.io.File; |
| 31 | +import java.io.IOException; |
| 32 | +import java.nio.file.Files; |
| 33 | +import java.nio.file.Path; |
| 34 | +import java.util.ArrayList; |
| 35 | +import java.util.List; |
| 36 | + |
| 37 | +/** |
| 38 | + * The full-set ledger-ingest entry point: loads a real protobuf artifact into an |
| 39 | + * ephemeral store, decompiles it via the #869 generator ({@link TaxonomySectioner} + |
| 40 | + * {@link SectionEmitter}), and writes one compilable Java source file per section — |
| 41 | + * turning {@code GeneratorEndToEndIT}'s proven, in-memory round trip into real, |
| 42 | + * persisted ledger source for a genesis repo's own ingested-foundation section |
| 43 | + * (IKE-Network/ike-issues#872). |
| 44 | + * <p> |
| 45 | + * Like {@link KbAssembleMain}/{@link BindingsMain}, a reflective seam for build |
| 46 | + * tooling: run via {@code exec:java} over a classpath carrying the entity, executor, |
| 47 | + * and ephemeral-store providers with their legacy {@code META-INF/services} |
| 48 | + * registrations. Unlike those, this is intentionally a one-time-per-target-repo |
| 49 | + * operation, not a repeated build step — the ingested-foundation section it produces |
| 50 | + * is regeneration-locked once landed (declared identities, not re-derived on every |
| 51 | + * build; future upstream updates arrive as change sets via the #847 lift machinery, |
| 52 | + * never by re-running this generator). |
| 53 | + * <p> |
| 54 | + * Arguments: {@code pbZip outputSourceRoot packageName moduleRef authorRef}. |
| 55 | + * {@code moduleRef}/{@code authorRef} are fully-qualified Java source expressions |
| 56 | + * (e.g. {@code network.ike.foundation.ike.terms.Ike.MODULE}) spliced literally into |
| 57 | + * every generated section's stamp — see {@link SectionEmitter#emitSection}. |
| 58 | + */ |
| 59 | +public final class LedgerGeneratorMain { |
| 60 | + |
| 61 | + private LedgerGeneratorMain() { |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * Generates one section source file per taxonomy bucket (plus a residual |
| 66 | + * catch-all), and a delegating aggregator that composes them all onto a |
| 67 | + * caller-supplied {@code KnowledgeSet}. |
| 68 | + * |
| 69 | + * @param args {@code pbZip outputSourceRoot packageName moduleRef authorRef} |
| 70 | + * @throws Exception if the store cannot be opened, the artifact fails to load, a |
| 71 | + * section cannot be written, or any component needed hand |
| 72 | + * authoring (zero tolerance for a silently-skipped component in |
| 73 | + * a real ingest) |
| 74 | + */ |
| 75 | + public static void main(String[] args) throws Exception { |
| 76 | + if (args.length != 5) { |
| 77 | + throw new IllegalArgumentException("Usage: LedgerGeneratorMain <pbZip> <outputSourceRoot>" |
| 78 | + + " <packageName> <moduleRef> <authorRef>"); |
| 79 | + } |
| 80 | + File pbZip = new File(args[0]); |
| 81 | + Path outputSourceRoot = Path.of(args[1]); |
| 82 | + String packageName = args[2]; |
| 83 | + String moduleRef = args[3]; |
| 84 | + String authorRef = args[4]; |
| 85 | + if (!pbZip.isFile()) { |
| 86 | + throw new IllegalArgumentException("Not a file: " + pbZip); |
| 87 | + } |
| 88 | + |
| 89 | + CachingService.clearAll(); |
| 90 | + PrimitiveData.selectControllerByName("Load Ephemeral Store"); |
| 91 | + PrimitiveData.start(); |
| 92 | + List<String> sectionClassNames = new ArrayList<>(); |
| 93 | + int distinctMembers; |
| 94 | + try { |
| 95 | + new LoadEntitiesFromProtobufFile(pbZip).compute(); |
| 96 | + |
| 97 | + StampCalculator calculator = Calculators.Stamp.DevelopmentLatestActiveOnly(); |
| 98 | + TinkarTermReferenceResolver resolver = TinkarTermReferenceResolver.build(); |
| 99 | + LanguageCalculator languageCalculator = |
| 100 | + Calculators.Language.UsEnglishFullyQualifiedName(calculator.stampCoordinate()); |
| 101 | + TaxonomySectioner sectioner = TaxonomySectioner.fromStatedNavigation(calculator); |
| 102 | + |
| 103 | + List<Section> sections = sectioner.sectionsCoveringFullStore( |
| 104 | + TinkarTerm.ROOT_VERTEX.nid(), 60, 4, 50); |
| 105 | + distinctMembers = sections.stream().mapToInt(section -> section.members().size()).sum(); |
| 106 | + |
| 107 | + List<String> emissionNotes = new ArrayList<>(); |
| 108 | + int index = 0; |
| 109 | + for (Section section : sections) { |
| 110 | + index++; |
| 111 | + if (section.members().isEmpty()) { |
| 112 | + continue; |
| 113 | + } |
| 114 | + String className = "Section" + index; |
| 115 | + sectionClassNames.add(className); |
| 116 | + SectionEmitter.EmittedSection emitted = SectionEmitter.emitSection(packageName, className, section, |
| 117 | + calculator, languageCalculator, resolver, moduleRef, authorRef); |
| 118 | + emissionNotes.addAll(emitted.manifestNotes()); |
| 119 | + writeSourceFile(outputSourceRoot, packageName, className, emitted.source()); |
| 120 | + } |
| 121 | + if (!emissionNotes.isEmpty()) { |
| 122 | + throw new IllegalStateException("Zero tolerance for a silently-skipped component in a real" |
| 123 | + + " ingest — " + emissionNotes.size() + " manifest note(s):\n" |
| 124 | + + String.join("\n", emissionNotes)); |
| 125 | + } |
| 126 | + |
| 127 | + String aggregatorSource = emitDelegatingAggregator(packageName, sectionClassNames); |
| 128 | + writeSourceFile(outputSourceRoot, packageName, "FoundationSet", aggregatorSource); |
| 129 | + } finally { |
| 130 | + PrimitiveData.stop(); |
| 131 | + } |
| 132 | + System.out.println("Ledger generated: " + sectionClassNames.size() + " sections, " |
| 133 | + + distinctMembers + " distinct components, into " + outputSourceRoot); |
| 134 | + } |
| 135 | + |
| 136 | + /** |
| 137 | + * Emits the delegating aggregator: unlike {@link SectionEmitter#emitAggregator} |
| 138 | + * (which mints its own fresh {@code KnowledgeSet}), this composes every section |
| 139 | + * onto a {@code KnowledgeSet} the caller already created — a genesis repo's own |
| 140 | + * session-root constant, not a new identity. |
| 141 | + * |
| 142 | + * @param packageName the target package |
| 143 | + * @param sectionClassNames every emitted section class's simple name, in |
| 144 | + * composition order |
| 145 | + * @return the aggregator class's full compilable source text |
| 146 | + */ |
| 147 | + private static String emitDelegatingAggregator(String packageName, List<String> sectionClassNames) { |
| 148 | + StringBuilder source = new StringBuilder(); |
| 149 | + source.append("package ").append(packageName).append(";\n\n"); |
| 150 | + source.append("import dev.ikm.tinkar.entity.builder.KnowledgeSet;\n\n"); |
| 151 | + source.append("/** Composes every ingested-foundation section onto the caller's KnowledgeSet" |
| 152 | + + " (IKE-Network/ike-issues#872). */\n"); |
| 153 | + source.append("public final class FoundationSet {\n\n"); |
| 154 | + source.append(" private FoundationSet() {\n }\n\n"); |
| 155 | + source.append(" public static void compose(KnowledgeSet set) {\n"); |
| 156 | + for (String sectionClassName : sectionClassNames) { |
| 157 | + source.append(" ").append(sectionClassName).append(".compose(set);\n"); |
| 158 | + } |
| 159 | + source.append(" }\n}\n"); |
| 160 | + return source.toString(); |
| 161 | + } |
| 162 | + |
| 163 | + private static void writeSourceFile(Path sourceRoot, String packageName, String className, String source) |
| 164 | + throws IOException { |
| 165 | + Path packageDir = sourceRoot.resolve(packageName.replace('.', '/')); |
| 166 | + Files.createDirectories(packageDir); |
| 167 | + Files.writeString(packageDir.resolve(className + ".java"), source); |
| 168 | + } |
| 169 | +} |
0 commit comments