001package Torello.Browser; 002 003import Torello.Java.StringParse; 004 005/** 006 * Converts Chrome DevTools Protocol data objects and builders into readable diagnostic text. 007 * 008 * <BR /><BR /> 009 * This class provides the shared implementation used by {@link BaseType#toString()} and by 010 * {@link AbstractBuilder#toString()}. It walks the descriptor metadata associated with a CDP 011 * data-carrier object or builder, retrieves each field or assigned value, and prints the result 012 * using the compact type information defined by {@link CDPTypes}. 013 * 014 * <BR /><BR /> 015 * The implementation is intentionally split across three small internal-support classes: 016 * 017 * <BR /><BR /> 018 * <UL CLASS=JDUL> 019 * 020 * <LI><B><CODE>CDPToString</CODE></B> controls the overall formatting process.</LI> 021 * 022 * <LI> <B><CODE>ParamRec</CODE></B> stores the current object, descriptor, indentation, padding, 023 * and value-retrieval function for one formatting pass. 024 * </LI> 025 * 026 * <LI> <B><CODE>ValSwitch</CODE></B> performs the type-byte dispatch needed to print primitive 027 * values, strings, arrays, raw JSON values, and nested CDP data types. 028 * </LI> 029 * </UL> 030 * 031 * <BR /><BR /> 032 * 💡 The important design point is that both {@code BaseType<?>} instances and 033 * {@code AbstractBuilder<?>} instances can be printed through the same descriptor-driven pathway. 034 * The only meaningful difference is how the value for each descriptor entry is retrieved. 035 */ 036@Torello.JavaDoc.Annotations.JDHeaderBackgroundImg(EmbedTagFileID="INTERNAL_USE_JDHBI") 037public class CDPToString 038{ 039 private CDPToString() { } 040 041 042 // ******************************************************************************************** 043 // ******************************************************************************************** 044 // Actual Entry Points 045 // ******************************************************************************************** 046 // ******************************************************************************************** 047 048 049 // Called from class 'AbstractBuilder' 050 static String toString(final AbstractBuilder<?> AB_THIS) 051 { return toString(new ParamRec(AB_THIS)); } 052 053 // Called from class 'BaseType' 054 static String toString(final BaseType<?> BT_THIS) 055 { 056 // Only Marker Events have "empty" classes. Such events will never be nested inside of 057 // another class. This issue has already been checked in the surrounding code. 058 059 return (BT_THIS.numFields == 0) 060 ? BT_THIS.getClass().getName() + ": Marker-Event, w/ 0 Fields\n" 061 : toString(new ParamRec(BT_THIS)); 062 } 063 064 065 // ******************************************************************************************** 066 // ******************************************************************************************** 067 // "Main" Method 068 // ******************************************************************************************** 069 // ******************************************************************************************** 070 071 072 // This is not private, because it is used inside 'ValSwitch' 073 // It is used to recursively print BaseType<?> and BaseType<?>[] 074 // Otherwise this would look much better if it were private. 075 076 static String toString(final ParamRec rec) 077 { 078 for (int i=0; i < rec.NUM; i++) 079 { 080 final Object value; 081 082 value = rec.getValue.apply(i); 083 084 rec.sb 085 .append(rec.INDENT) 086 .append(StringParse.rightSpacePad(rec.descriptor.names.get(i), rec.NAME_PADDING)); 087 088 final byte typeByte = rec.descriptor.types.get(i); 089 090 091 // After 2 hours of arguing with Chat-GPT, it wrote this to describe what it just spent 092 // the afternoon doing. Looks great if you ask me... 093 // 094 // IMPORTANT: 095 // 096 // Primitive/String/Number types can recover their printable type-name directly 097 // from the CDPTypes lookup tables. 098 // 099 // HOWEVER: 100 // 101 // Complex nested CDP types (CDP_TYPE / CDP_TYPE_ARRAY_1D) do not store their 102 // concrete Java class names inside the descriptor metadata itself. 103 // 104 // In those cases, the descriptor's package-private "getClass(...)" reflection 105 // method is used to recover the underlying Java type. 106 // 107 // This allows null-values to still print meaningful type information such as: 108 // 109 // RunTime.PropertyPreview[] 110 // Network.CookiePartitionKey 111 // 112 // rather than merely: 113 // 114 // CDP_TYPE 115 // CDP_TYPE_ARRAY_1D 116 // 117 // NOTE: 118 // 119 // CommandDescriptor reflection is intentionally limited to locating the actual 120 // Script-returning command method, NOT the zero-argument CommandBuilder factory 121 // method generated for convenience. 122 123 if (value != null) 124 ValSwitch.print(rec, value, typeByte); 125 126 else 127 { 128 final String cdpTypeStr = CDPTypes.types.get(typeByte); 129 130 final String typeStr = (cdpTypeStr != null) 131 ? cdpTypeStr 132 : className(rec.descriptor.getClass(rec.descriptor.names.get(i), i)); 133 134 rec.sb.append( 135 "null (" + typeStr + ", " + 136 (rec.descriptor.optionals.get(i) ? "" : "not ") + 137 "declared optional)\n" 138 ); 139 } 140 } 141 142 final String I = StringParse.nChars(' ', rec.CURRENT_INDENT - 4); 143 144 return 145 rec.printedName + ":\n" + 146 I + "{\n" + 147 rec.sb.toString() + 148 I + "}\n"; 149 } 150 151 152 // ******************************************************************************************** 153 // ******************************************************************************************** 154 // Static Helpers 155 // ******************************************************************************************** 156 // ******************************************************************************************** 157 158 159 // This helper intentionally preserves nested-class '.' notation. 160 // 161 // Java reflection APIs often emit nested types using '$': 162 // ➡️ RunTime$ObjectPreview 163 // 164 // whereas this utility converts the output into: 165 // ➡️ RunTime.ObjectPreview 166 // 167 // which is substantially more readable in generated diagnostics. 168 // 169 // Array types are recursively normalized so that: 170 // ➡️ RunTime.PropertyPreview[] 171 // 172 // is emitted instead of JVM-style array descriptors. 173 174 static String className(final Class<?> c) 175 { 176 if (c.isArray()) return className(c.getComponentType()) + "[]"; 177 178 final String canonical = c.getCanonicalName(); 179 final Package pkg = c.getPackage(); 180 181 if (canonical == null) throw new CDPToStringError("Canonical Name is null: " + c); 182 if (pkg == null) throw new CDPToStringError("Package is null: " + canonical); 183 184 return canonical.substring(pkg.getName().length() + 1); 185 } 186 187 private static class CDPToStringError extends Error 188 { 189 private static final long serialVersionUID = 1L; 190 CDPToStringError(final String s) { super(s); } 191 } 192 193}