package it.cavallium.data.generator.plugin; import static java.util.Objects.requireNonNull; import static java.util.function.Function.identity; import it.cavallium.data.generator.plugin.ComputedType.VersionedComputedType; import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectCollection; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.LongAdder; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; public class DataModel { private static final List NATIVE_TYPES = List.of("String", "boolean", "short", "char", "int", "long", "float", "double", "byte", "Int52" ); private final ComputedVersion currentVersion; private final int hash; private final Map interfacesData; private final Int2ObjectMap versions; private final Map> superTypes; private final Map customTypes; private final Int2ObjectMap> computedTypes; private final Map versionedTypePrevVersion; private final Map versionedTypeNextVersion; public DataModel(int hash, String currentVersionKey, Map interfacesData, Map baseTypesData, Map> superTypesData, Map customTypesData, Map rawVersions) { this.hash = hash; if (rawVersions.isEmpty()) { throw new IllegalArgumentException("No defined versions"); } if (!rawVersions.containsKey(currentVersionKey)) { throw new IllegalArgumentException("Current version " + currentVersionKey + " is not defined in versions!"); } // Check if there are multiple root versions String rootVersion = rawVersions.entrySet().stream() .filter(e -> e.getValue().previousVersion == null) .map(Entry::getKey) .collect(toSingleton(DataModel::throwMultiRootVersions)); // Check if multiple versions depend on the same version rawVersions.entrySet().stream() .filter(v -> v.getValue().previousVersion != null) .collect(Collectors.groupingBy(v -> v.getValue().previousVersion)) .entrySet() .stream() .filter(x -> x.getValue().size() > 1) .forEach(x -> { throw new IllegalArgumentException("Multiple versions depend on version " + x.getKey() + ": " + x.getValue() .stream().map(Entry::getKey).collect(Collectors.joining(", "))); }); // Create the next versions map Map nextVersionMap = rawVersions.keySet().stream() .map(version -> Map.entry(version, rawVersions .entrySet() .stream() .filter(x -> Objects.equals(version, x.getValue().previousVersion)) .map(Entry::getKey) .collect(toOptional(nextVersions -> throwMultiNextVersions(version, nextVersions)))) ) .filter(x -> x.getValue().isPresent()) .map(x -> Map.entry(x.getKey(), x.getValue().get())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); // Build versions sequence int versionsCount = 0; Int2ObjectMap versionToName = new Int2ObjectLinkedOpenHashMap<>(); Object2IntMap nameToVersion = new Object2IntOpenHashMap<>(); { String lastVersion = null; String nextVersion = rootVersion; while (nextVersion != null) { lastVersion = nextVersion; versionToName.put(versionsCount, nextVersion); nameToVersion.put(nextVersion, versionsCount); nextVersion = getNextVersion(nextVersionMap, nextVersion); versionsCount++; } if (!Objects.equals(lastVersion, currentVersionKey)) { throw new IllegalArgumentException("Last version " + lastVersion + " is different than the defined current version: " + currentVersionKey); } } int latestVersion = versionsCount - 1; // Collect all existing base types List baseTypes = new ArrayList<>(baseTypesData.keySet()); // Collect all existing super types List superTypes = new ArrayList<>(superTypesData.keySet()); // Collect all custom types List customTypes = new ArrayList<>(customTypesData.keySet()); // Compute all types, excluding nullables and arrays List allTypes = Stream.concat(Stream.concat(Stream.concat(baseTypes.stream(), superTypes.stream()), customTypes.stream()), NATIVE_TYPES.stream()) .distinct() .toList(); Stream.concat(Stream.concat(Stream.concat(baseTypes.stream(), superTypes.stream()), customTypes.stream()), NATIVE_TYPES.stream()) .collect(Collectors.groupingBy(identity())) .values() .stream() .filter(x -> x.size() > 1) .forEach(x -> { var type = x.get(0); throw new IllegalArgumentException("Type " + type + " has been defined more than once (check base, super, and custom types)!"); }); // Compute the numeric versions map Int2ObjectMap parsedVersions = new Int2ObjectLinkedOpenHashMap<>(); rawVersions.forEach((k, v) -> parsedVersions.put(nameToVersion.getInt(k), new ParsedVersion(v))); Int2ObjectMap> computedClassConfig = new Int2ObjectLinkedOpenHashMap<>(); for (int versionIndex = 0; versionIndex < versionsCount; versionIndex++) { if (versionIndex == 0) { computedClassConfig.put(0, baseTypesData.entrySet().stream() .map(e -> Map.entry(e.getKey(), new ParsedClass(e.getValue()))) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)) ); } else { var version = parsedVersions.get(versionIndex); Map prevVersionConfiguration = requireNonNull(computedClassConfig.get(versionIndex - 1)); Map newVersionConfiguration = prevVersionConfiguration.entrySet().stream() .map(entry -> Map.entry(entry.getKey(), entry.getValue().clone())) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); for (VersionTransformation rawTransformation : version.transformations) { TransformationConfiguration transformation; var transformCoordinate = "Transformation at index " + version.transformations.indexOf(rawTransformation) + " in version " + versionToName.get(versionIndex); try { transformation = rawTransformation.getTransformation(); } catch (Exception ex) { throw new IllegalArgumentException(transformCoordinate + " is not consistent", ex); } final String transformName = transformation.getTransformName(); switch (transformName) { case "move-data" -> { var t = (MoveDataConfiguration) transformation; var transformClass = newVersionConfiguration.get(t.transformClass); if (transformClass == null) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.transformClass); } transformClass.differentThanPrev = true; var definition = removeAndGetIndex(transformClass.data, t.from); if (definition.isEmpty()) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown field: " + t.from); } var prevDef = tryInsertAtIndex(transformClass.data, t.to, definition.get().getValue(), definition.get().getKey() ); if (prevDef != null) { throw new IllegalArgumentException( transformCoordinate + " tries to overwrite the existing field \"" + t.to + "\" of value \"" + prevDef + "\" with the field \"" + t.from + "\" of type \"" + definition.orElse(null) + "\""); } } case "new-data" -> { var t = (NewDataConfiguration) transformation; var transformClass = newVersionConfiguration.get(t.transformClass); if (transformClass == null) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.transformClass); } transformClass.differentThanPrev = true; if (!allTypes.contains(extractTypeName(t.type))) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.type); } String prevDef; if (t.index != null) { prevDef = tryInsertAtIndex(transformClass.data, t.to, fixType(t.type), t.index); } else { prevDef = transformClass.data.putIfAbsent(t.to, fixType(t.type)); } if (prevDef != null) { throw new IllegalArgumentException(transformCoordinate + " tries to overwrite the existing field \"" + t.to + "\" of value \"" + prevDef + "\" with the new type \"" + t.type + "\""); } } case "remove-data" -> { var t = (RemoveDataConfiguration) transformation; var transformClass = newVersionConfiguration.get(t.transformClass); if (transformClass == null) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.transformClass); } transformClass.differentThanPrev = true; var prevDef = transformClass.data.remove(t.from); if (prevDef == null) { throw new IllegalArgumentException(transformCoordinate + " tries to remove the nonexistent field \"" + t.from + "\""); } } case "upgrade-data" -> { var t = (UpgradeDataConfiguration) transformation; var transformClass = newVersionConfiguration.get(t.transformClass); if (transformClass == null) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.transformClass); } transformClass.differentThanPrev = true; if (!allTypes.contains(extractTypeName(t.type))) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown type: " + t.type); } String prevDefinition = transformClass.data.replace(t.from, fixType(t.type)); if (prevDefinition == null) { throw new IllegalArgumentException(transformCoordinate + " refers to an unknown field: " + t.from); } } default -> throw new IllegalArgumentException("Unknown transform name: "+ transformName); } } computedClassConfig.put(versionIndex, newVersionConfiguration); } } // Compute the versions var computedVersions = parsedVersions .int2ObjectEntrySet() .stream() .collect(Collectors.toMap(Int2ObjectMap.Entry::getIntKey, e -> new ComputedVersion(e.getValue(), e.getIntKey(), e.getIntKey() == latestVersion, versionToName.get(e.getIntKey()) ), (a, b) -> { throw new IllegalStateException(); }, Int2ObjectLinkedOpenHashMap::new )); // Compute the types Int2ObjectMap> computedTypes = new Int2ObjectLinkedOpenHashMap<>(); Int2ObjectMap> randomComputedTypes = new Int2ObjectOpenHashMap<>(); ComputedTypeSupplier computedTypeSupplier = new ComputedTypeSupplier(randomComputedTypes, computedVersions.get(latestVersion)); { for (int versionNumber = latestVersion - 1; versionNumber >= 0; versionNumber--) { var version = computedClassConfig.get(versionNumber); computedClassConfig.get(versionNumber + 1).forEach((type, typeConfig) -> { if (typeConfig.differentThanPrev) { version.get(type).differentThanNext = true; } }); } for (int versionIndex = latestVersion; versionIndex >= 0; versionIndex--) { int versionIndexF = versionIndex; var version = computedVersions.get(versionIndexF); if (versionIndexF == latestVersion) { // Compute base types List versionBaseTypes = computedClassConfig.get(versionIndexF).entrySet().stream() .map(e -> { var data = new LinkedHashMap(); e.getValue().getData().forEach((key, value) -> data.put(key, new VersionedType(value, version))); return new ComputedTypeBase(new VersionedType(e.getKey(), version), e.getValue().stringRepresenter, data, computedTypeSupplier); }).collect(Collectors.toList()); // Compute custom types customTypesData.forEach((name, data) -> versionBaseTypes.add(new ComputedTypeCustom(name, data.getJavaClassString(), data.serializer, computedTypeSupplier, computedVersions.get(latestVersion)))); // Compute super types superTypesData.forEach((key, data) -> { List subTypes = data.stream().map(x -> new VersionedType(x, version)).toList(); versionBaseTypes.add(new ComputedTypeSuper(new VersionedType(key, version), subTypes, computedTypeSupplier)); }); // Compute nullable types { var nullableRawTypes = computedClassConfig.values().stream() .flatMap(x -> x.values().stream()) .flatMap(x -> x.getData().values().stream()) .filter(x -> x.startsWith("-")) .map(nullableName -> nullableName.substring(1)) .toList(); // Compute nullable base types nullableRawTypes.stream() .filter(nullableName -> !NATIVE_TYPES.contains(nullableName)) .map(nullableName -> new VersionedType(nullableName, version)) .map(baseType -> new ComputedTypeNullableVersioned(baseType, computedTypeSupplier)) .forEach(versionBaseTypes::add); // Compute nullable native types nullableRawTypes.stream() .filter(NATIVE_TYPES::contains) .map(baseType -> new ComputedTypeNullableNative(baseType, computedVersions.get(latestVersion), computedTypeSupplier)) .forEach(versionBaseTypes::add); } // Compute array types { var arrayRawTypes = computedClassConfig.values().stream() .flatMap(x -> x.values().stream()) .flatMap(x -> x.getData().values().stream()) .filter(x -> x.startsWith("§")) .map(nullableName -> nullableName.substring(1)) .toList(); // Compute array base types arrayRawTypes.stream() .filter(nullableName -> !NATIVE_TYPES.contains(nullableName)) .map(nullableName -> new VersionedType(nullableName, version)) .map(baseType -> new ComputedTypeArrayVersioned(baseType, computedTypeSupplier)) .forEach(versionBaseTypes::add); // Compute array native types arrayRawTypes.stream() .filter(NATIVE_TYPES::contains) .map(baseType -> new ComputedTypeArrayNative(baseType, computedTypeSupplier)) .forEach(versionBaseTypes::add); } // Compute native types versionBaseTypes.addAll(ComputedTypeNative.get(computedTypeSupplier)); randomComputedTypes.put(versionIndexF, versionBaseTypes.stream().distinct().collect(Collectors.toMap(ComputedType::getName, identity()))); } else { Set changedTypes = randomComputedTypes.get(versionIndexF + 1).values().stream() .filter(prevType -> prevType instanceof ComputedTypeBase prevBaseType && computedClassConfig.get(versionIndexF).get(prevBaseType.getName()).differentThanNext) .map(ComputedType::getName) .collect(Collectors.toSet()); { boolean addedMoreTypes; do { var newChangedTypes = changedTypes .parallelStream() .flatMap(changedType -> randomComputedTypes.get(versionIndexF + 1).get(changedType).getDependents()) .map(ComputedType::getName) .distinct() .toList(); addedMoreTypes = changedTypes.addAll(newChangedTypes); } while (addedMoreTypes); } Map currentVersionComputedTypes = new HashMap<>(); var versionChangeChecker = new VersionChangeChecker(changedTypes, versionIndexF, latestVersion); randomComputedTypes.get(versionIndexF + 1).forEach((name, type) -> { if (!changedTypes.contains(name)) { currentVersionComputedTypes.put(name, type); } else { if (type instanceof VersionedComputedType versionedComputedType) { ComputedType newType = versionedComputedType.withChangeAtVersion(version, versionChangeChecker); currentVersionComputedTypes.put(name, newType); } else { throw new IllegalStateException(); } } }); randomComputedTypes.put(versionIndexF, currentVersionComputedTypes); } } for (int i = 0; i < versionsCount; i++) { computedTypes.put(i, Objects.requireNonNull(randomComputedTypes.get(i))); } } // All types, including arrays, nullables, primitives, etc var allComputedTypes = computedTypes.values().stream().flatMap(x -> x.values().stream()).distinct().toList(); // Compute the upgrade paths Map versionedTypeNextVersion = new HashMap<>(); Map versionedTypePrevVersion = new HashMap<>(); Map> versionedTypeVersions = allComputedTypes .stream() .filter(x -> x instanceof VersionedComputedType) .map(x -> (VersionedComputedType) x) .collect(Collectors.groupingBy(ComputedType::getName)) .entrySet() .stream() .collect(Collectors.toMap(Entry::getKey, e -> e .getValue() .stream() .sorted(Comparator.comparingInt(x -> x.getVersion().getVersion())) .map(x -> new VersionedType(e.getKey(), x.getVersion())) .toList())); versionedTypeVersions.forEach((type, versionsList) -> { VersionedType prev = null; for (VersionedType versionedType : versionsList) { if (prev != null) { versionedTypePrevVersion.put(versionedType, prev); } prev = versionedType; } prev = null; for (int i = versionsList.size() - 1; i >= 0; i--) { var versionedType = versionsList.get(i); if (prev != null) { versionedTypeNextVersion.put(versionedType, prev); } prev = versionedType; } }); /* Example upgrade: V001====================================== _Message v1 |__MessageForwardOrigin v1 |__MessageText v1 _MessageText _MessageForwardOrigin v1 |__MessageForwardOriginChat v1 _MessageForwardOriginChat v1 |__ChatEntityId v1 _UserId v1 _ChatEntityId v1 |__UserId v1 |__SupergroupId v1 |__BaseGroupId v1 V002====================================== * UserId changed ========================================== _Message v2 * |__MessageForwardOrigin v2 * |__MessageText v1 _MessageText v1 _MessageForwardOrigin v2 * |__MessageForwardOriginChat v2 * _MessageForwardOriginChat v2 * |__ChatEntityId v2 * _UserId v2 * _ChatEntityId v2 * |__UserId v2 * |__SupergroupId v1 |__BaseGroupId v1 */ this.versions = computedVersions; this.interfacesData = interfacesData.entrySet().stream() .map(e -> Map.entry(e.getKey(), new ParsedInterface(e.getValue()))) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); LongAdder unchangedTot = new LongAdder(); LongAdder changedTot = new LongAdder(); computedTypes.forEach((version, types) -> { System.out.println("Version: " + version); System.out.println("\tTypes: " + types.size()); System.out.println("\tVersioned types: " + types.values().stream().filter(t -> (t instanceof VersionedComputedType)).count()); var unchanged = types.values().stream().filter(t -> (t instanceof VersionedComputedType versionedComputedType && versionedComputedType.getVersion().getVersion() != version)).count(); var changed = types.values().stream().filter(t -> (t instanceof VersionedComputedType versionedComputedType && versionedComputedType.getVersion().getVersion() == version)).count(); unchangedTot.add(unchanged); changedTot.add(changed); System.out.println("\t\tUnchanged: " + unchanged + " (" + (unchanged * 100 / Math.max(changed + unchanged, 1)) + "%)"); System.out.println("\t\tChanged: " + changed + " (" + (changed * 100 / Math.max(changed + unchanged, 1)) + "%)"); }); System.out.println("Result:"); var unchanged = unchangedTot.sum(); var changed = changedTot.sum(); System.out.println("\tAvoided type versions: " + unchanged + " (" + (unchanged * 100 / (changed + unchanged)) + "%)"); System.out.println("\tType versions: " + changed + " (" + (changed * 100 / (changed + unchanged)) + "%)"); this.currentVersion = computedVersions.get(versionsCount - 1); this.superTypes = superTypesData; this.customTypes = customTypesData; this.computedTypes = computedTypes; this.versionedTypePrevVersion = versionedTypePrevVersion; this.versionedTypeNextVersion = versionedTypeNextVersion; } private String tryInsertAtIndex(LinkedHashMap data, String key, String value, int index) { var before = new LinkedHashMap(); var after = new LinkedHashMap(); int i = 0; for (Entry entry : data.entrySet()) { if (i < index) { before.put(entry.getKey(), entry.getValue()); } else { after.put(entry.getKey(), entry.getValue()); } i++; } data.clear(); data.putAll(before); var prev = data.putIfAbsent(key, value); data.putAll(after); return prev; } private Optional> removeAndGetIndex(LinkedHashMap data, String find) { int foundIndex = -1; { int i = 0; for (Entry entry : data.entrySet()) { if (entry.getKey().equals(find)) { foundIndex = i; } i++; } } if (foundIndex == -1) return Optional.empty(); return Optional.of(Map.entry(foundIndex, requireNonNull(data.remove(find)))); } @Nullable public static String getNextVersion(Map versionsSequence, String version) { return versionsSequence.get(version); } private static RuntimeException throwMultiNextVersions(String version, List nextVersions) { return new IllegalArgumentException("Found many next versions of version " + version + ":" + String.join(", ", nextVersions)); } private static RuntimeException throwMultiRootVersions(List rootVersions) { return new IllegalArgumentException("Found many root versions: " + String.join(", ", rootVersions)); } public static Collector toSingleton() { return toSingleton(x -> new IllegalStateException()); } public static Collector toSingleton(Function, RuntimeException> exceptionGenerator) { return Collectors.collectingAndThen( Collectors.toList(), list -> { if (list.size() != 1) { throw exceptionGenerator.apply(list); } return list.get(0); } ); } public static Collector> toOptional() { return toOptional(x -> new IllegalStateException()); } public static Collector> toOptional(Function, RuntimeException> exceptionGenerator) { return Collectors.collectingAndThen( Collectors.toList(), list -> { if (list.size() > 1) { throw exceptionGenerator.apply(list); } if (list.isEmpty()) { return Optional.empty(); } return Optional.of(list.get(0)); } ); } public int computeHash() { return hash; } public Set> getInterfacesSet() { return interfacesData.entrySet(); } public static String fixType(String fieldType) { if (fieldType.endsWith("[]") && fieldType.startsWith("-")) { throw new UnsupportedOperationException("Arrays cannot be null"); } if (fieldType.endsWith("[]")) { return "§" + fieldType.substring(0, fieldType.length() - 2); } else { return fieldType; } } /** * Get the base type * X --> X * -X --> X * X[] --> X */ public static String extractTypeName(String fieldType) { if (fieldType.endsWith("[]") && fieldType.startsWith("-")) { throw new UnsupportedOperationException("Arrays cannot be null"); } if (fieldType.endsWith("[]")) { return fieldType.substring(0, fieldType.length() - 2); } else if (fieldType.startsWith("-")) { return fieldType.substring(1); } else { return fieldType; } } public ObjectCollection getVersionsSet() { return this.versions.values(); } public int getCurrentVersionNumber() { return currentVersion.getVersion(); } public ComputedVersion getCurrentVersion() { return currentVersion; } @Deprecated public Map> getSuperTypesRaw() { return this.superTypes; } public Stream getSuperTypesComputed() { return getSuperTypesComputed(currentVersion); } public Stream getSuperTypesComputed(ComputedVersion version) { return this.computedTypes.get(version.getVersion()).values().stream() .filter(t -> t instanceof ComputedTypeSuper).map(t -> (ComputedTypeSuper) t); } public Stream getBaseTypesComputed() { return getBaseTypesComputed(currentVersion); } public Stream getBaseTypesComputed(ComputedVersion version) { return this.computedTypes.get(version.getVersion()).values().stream() .filter(t -> t instanceof ComputedTypeBase).map(t -> (ComputedTypeBase) t); } public Optional getNextVersion(ComputedVersion versionConfiguration) { var nextVersion = versions.get(versionConfiguration.getVersion() + 1); return Optional.ofNullable(nextVersion); } public ComputedVersion getNextVersionOrThrow(ComputedVersion versionConfiguration) { return Objects.requireNonNull(versions.get(versionConfiguration.getVersion() + 1)); } public Map getInterfaces() { return interfacesData; } @Deprecated public Map getCustomTypes() { return customTypes; } public Int2ObjectMap> getComputedTypes() { return computedTypes; } public Map getComputedTypes(ComputedVersion version) { return computedTypes.get(version.getVersion()); } public VersionedType getNextVersion(VersionedType type) { return versionedTypeNextVersion.get(type); } public VersionedType getPrevVersion(VersionedType type) { return versionedTypePrevVersion.get(type); } public T getNextVersion(T type) { if (type instanceof VersionedComputedType versionedComputedType) { var result = versionedTypeNextVersion.get(new VersionedType(versionedComputedType.getName(), versionedComputedType.getVersion())); if (result == null) { return null; } //noinspection unchecked return (T) this.computedTypes.get(result.version().getVersion()).get(result.type()); } else { return null; } } public T getPrevVersion(T type) { if (type instanceof VersionedComputedType versionedComputedType) { var result = versionedTypePrevVersion.get(new VersionedType(versionedComputedType.getName(), versionedComputedType.getVersion())); if (result == null) { return null; } //noinspection unchecked return (T) this.computedTypes.get(result.version().getVersion()).get(result.type()); } else { return null; } } public boolean isTypeForVersion(ComputedVersion versionConfiguration, String key) { var type = getComputedTypes(versionConfiguration).get(key); return type instanceof VersionedComputedType versionedComputedType && versionedComputedType.getVersion().getVersion() == versionConfiguration.getVersion(); } public ComputedVersion getTypeFirstSameVersion(VersionedComputedType type) { var prevVersion = getPrevVersion(type); if (prevVersion != null) { return versions.get(prevVersion.getVersion().getVersion() + 1); } else { return type.getVersion(); } } public Stream getTypeSameVersions(VersionedComputedType type) { var initialVersion = getTypeFirstSameVersion(type); var lastVersion = type.getVersion(); return getVersionRange(initialVersion, lastVersion); } @Deprecated public ComputedVersion getVersion(int version) { return Objects.requireNonNull(versions.get(version)); } public ComputedVersion getVersion(VersionedComputedType versionedComputedType) { return Objects.requireNonNull(versions.get(versionedComputedType.getVersion().getVersion())); } public Stream getVersionRange(ComputedVersion initialVersionInclusive, ComputedVersion lastVersionInclusive) { if (initialVersionInclusive.getVersion() > lastVersionInclusive.getVersion()) { throw new IllegalArgumentException(); } return IntStream .rangeClosed(initialVersionInclusive.getVersion(), lastVersionInclusive.getVersion()) .mapToObj(versions::get); } public String getVersionPackage(ComputedVersion version, String basePackageName) { return version.getPackage(basePackageName); } public String getVersionDataPackage(ComputedVersion version, String basePackageName) { return version.getDataPackage(basePackageName); } @Deprecated public String getVersionDataPackage(VersionedComputedType type, String basePackageName) { return type.getVersion().getDataPackage(basePackageName); } public String getRootPackage(String basePackageName) { return joinPackage(basePackageName, ""); } public static String joinPackage(String basePackageName, String packageName) { if (basePackageName.isBlank()) { basePackageName = "org.generated"; } if (packageName.isBlank()) { return basePackageName; } else { return basePackageName + "." + packageName; } } public Stream getSuperTypesOf(VersionedComputedType baseType) { return getSuperTypesComputed(baseType.getVersion()).filter(type -> type.subTypes().contains(baseType)); } public Stream getExtendsInterfaces(ComputedTypeSuper superType) { if (superType.getVersion().isCurrent()) { var interfaces = interfacesData.get(superType.getName()); if (interfaces != null) { return interfaces.extendInterfaces.stream() .map(name -> (ComputedTypeSuper) this.computedTypes.get(currentVersion.getVersion()).get(name)); } } return Stream.of(); } public Stream> getCommonInterfaceGetters(ComputedTypeSuper superType) { if (superType.getVersion().isCurrent()) { var interfaces = interfacesData.get(superType.getName()); if (interfaces != null) { return interfaces.commonGetters.entrySet().stream().map(x -> Map.entry(x.getKey(), this.computedTypes.get(currentVersion.getVersion()).get(x.getValue()))); } } return Stream.of(); } public Stream> getCommonInterfaceData(ComputedTypeSuper superType) { if (superType.getVersion().isCurrent()) { var interfaces = interfacesData.get(superType.getName()); if (interfaces != null) { return interfaces.commonData.entrySet().stream().map(x -> Map.entry(x.getKey(), this.computedTypes.get(currentVersion.getVersion()).get(x.getValue()))); } } return Stream.of(); } }