diff --git a/src/main/java/org/extendj/ragdoc/JsonBuilder.java b/src/main/java/org/extendj/ragdoc/JsonBuilder.java index 78ff6ec57c92136183aa04ae46dc47e8f47f14b8..0b0ac40f29914cf71597d25caa819789a3e0c63f 100644 --- a/src/main/java/org/extendj/ragdoc/JsonBuilder.java +++ b/src/main/java/org/extendj/ragdoc/JsonBuilder.java @@ -57,6 +57,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; public class JsonBuilder { private Map<String, String> typeIndex = new HashMap<>(); @@ -65,6 +66,7 @@ public class JsonBuilder { private Set<String> aspects = new HashSet<>(); final Map<String, JsonArray> packages = new HashMap<>(); final Map<String, Collection<TypeDecl>> packageTypeMap = new HashMap<>(); + final Map<TypeDecl, Collection<TypeDecl>> subtypeMap = new HashMap<>(); /** Ordering of the package object kinds. */ private static final String[] TYPE_KINDS = { "ast-class", "interface", "class" }; @@ -144,7 +146,7 @@ public class JsonBuilder { } } - private JsonValue typeRef(TypeDecl type) { + private JsonObject typeRef(TypeDecl type) { // TODO: handle wildcard types. JsonObject obj = new JsonObject(); if (shouldDocument(type)) { @@ -201,6 +203,14 @@ public class JsonBuilder { } // TODO: inner class names. + + /** + * Generates a unique suffix that is used to distinguish duplicate typenames. + * + * @param type the type to generate a unique name suffix for. + * @return {@code "%"} if the given type has a unique typename, otherwise + * {@code "%ID"} where the ID part is a unique integer. + */ String typeId(TypeDecl type) { String typename = type.baseTypeName(); if (typeIndex.containsKey(typename)) { @@ -336,10 +346,12 @@ public class JsonBuilder { ClassDecl klass = (ClassDecl) type; if (klass.hasSuperclass()) { obj.add("superclass", typeRef(klass.superclass())); + addSubtype(klass.superclass(), type); } JsonArray ifaces = new JsonArray(); for (Access access: klass.getImplementsList()) { ifaces.add(typeRef(access.type())); + addSubtype(access.type(), type); } if (!ifaces.isEmpty()) { obj.add("superinterfaces", ifaces); @@ -383,7 +395,20 @@ public class JsonBuilder { } /** - * This adds inherited members from superclasses. + * Registers subtype as being a subtype of type. + */ + private void addSubtype(TypeDecl type, TypeDecl subtype) { + type = type.original(); + Collection<TypeDecl> subtypes = subtypeMap.get(type); + if (subtypes == null) { + subtypes = new HashSet<>(); + subtypeMap.put(type, subtypes); + } + subtypes.add(subtype); + } + + /** + * Adds inherited members from superclasses. */ private void addInheritedMembers(TypeDecl type, JsonObject obj) { if (type instanceof ClassDecl) { @@ -478,9 +503,16 @@ public class JsonBuilder { if (groupMap.containsKey(kind)) { JsonObject group = new JsonObject(); group.add("kind", Json.of(kind)); - JsonArray members = groupMap.get(kind); - sortArrayBy(members, "name"); - group.add("members", members); + JsonArray sortedMembers = new JsonArray(); + KeyFun<JsonValue, String> keyFun = new KeyFun<JsonValue, String>() { + @Override public String apply(JsonValue value) { + return value.object().get("name").asString(""); + } + }; + for (JsonValue v : sortBy(groupMap.get(kind), keyFun)) { + sortedMembers.add(v); + } + group.add("members", sortedMembers); groups.add(group); } } @@ -489,7 +521,7 @@ public class JsonBuilder { public JsonArray packageIndex() { // TODO: split into separate arrays based on object kind. - JsonArray packageIndex = new JsonArray(); + List<JsonObject> packageIndex = new ArrayList<>(); for (String packageName : packages.keySet()) { // Group members by kind. Map<String, JsonArray> groupMap = new HashMap<>(); @@ -512,27 +544,62 @@ public class JsonBuilder { entry.add("groups", groups); packageIndex.add(entry); } - sortArrayBy(packageIndex, "name"); - return packageIndex; + JsonArray sorted = new JsonArray(); + KeyFun<JsonObject, String> keyFun = new KeyFun<JsonObject, String>() { + @Override public String apply(JsonObject value) { + return value.object().get("name").asString(""); + } + }; + for (JsonValue v : sortBy(packageIndex, keyFun)) { + sorted.add(v); + } + return sorted; + } + + public JsonArray subtypesJson(TypeDecl type) { + JsonArray subtypes = new JsonArray(); + if (subtypeMap.containsKey(type)) { + KeyFun<TypeDecl, String> keyFun = new KeyFun<TypeDecl, String>() { + @Override public String apply(TypeDecl type) { + return type.name(); + } + }; + for (TypeDecl subtype : sortBy(subtypeMap.get(type), keyFun)) { + subtypes.add(typeRef(subtype)); + } + } + return subtypes; + } + + public interface KeyFun<T, R> { + R apply(T t); } /** - * Sort object elements of JSON array by a particular key. + * Returns a sorted JSON array based on the argument elements and a key function. + * + * <p>The result preserves duplicate elements and elements with identical keys.</p> */ - static void sortArrayBy(JsonArray array, String key) { - Map<String, JsonObject> members = new HashMap<>(); - for (JsonValue value : array) { - JsonObject member = value.object(); - members.put(member.get(key).stringValue(""), member); + static <T, K extends Comparable<? super K>> List<T> sortBy( + Iterable<T> iterable, + KeyFun<T, K> keyFun) { + Map<K, Collection<T>> members = new TreeMap<>(); + for (T value : iterable) { + K sortKey = keyFun.apply(value); + Collection<T> items = members.get(sortKey); + if (items == null) { + items = new ArrayList<>(); + members.put(sortKey, items); + } + items.add(value); } - List<String> names = new ArrayList<>(members.keySet()); - Collections.sort(names); - int index = 0; - for (String name : names) { - JsonObject member = members.get(name); - array.set(index, member); - index += 1; + List<K> keys = new ArrayList<>(members.keySet()); + Collections.sort(keys); + ArrayList<T> result = new ArrayList<>(members.size()); + for (Collection<T> values : members.values()) { + result.addAll(values); } + return result; } } diff --git a/src/main/java/org/extendj/ragdoc/RagDocBuilder.java b/src/main/java/org/extendj/ragdoc/RagDocBuilder.java index bc48412975c0bf456f96f9d29d2fa1d45ae836eb..d2be8b2585678178bb43e392b70d11fa0a32a754 100644 --- a/src/main/java/org/extendj/ragdoc/RagDocBuilder.java +++ b/src/main/java/org/extendj/ragdoc/RagDocBuilder.java @@ -37,6 +37,7 @@ import org.extendj.ast.JavaParser; import org.extendj.ast.Problem; import org.extendj.ast.Program; import org.extendj.ast.TypeDecl; +import se.llbit.json.JsonArray; import se.llbit.json.JsonObject; import se.llbit.json.JsonValue; import se.llbit.json.PrettyPrinter; @@ -110,7 +111,10 @@ public class RagDocBuilder extends Frontend { for (Map.Entry<TypeDecl, JsonObject> entry : jsonBuilder.typemap.entrySet()) { TypeDecl type = entry.getKey(); JsonObject typeJson = entry.getValue(); - JsonBuilder.sortArrayBy(typeJson.get("members").array(), "name"); + JsonArray subtypes = jsonBuilder.subtypesJson(type); + if (!subtypes.isEmpty()) { + typeJson.add("subtypes", jsonBuilder.subtypesJson(type)); + } String fileName = type.name() + jsonBuilder.typeId(type).substring(1) + ".json"; outputJson(outputDir, fileName, typeJson); }