001package Torello.Browser; 002 003import static Torello.Java.StrPrint.I4; 004 005import java.lang.reflect.Method; 006import java.lang.reflect.Modifier; 007import java.lang.reflect.MalformedParametersException; 008 009import Torello.Java.ReadOnly.ReadOnlyArrayList; 010import Torello.Java.ReadOnly.ReadOnlyList; 011 012/** 013 * Reflected information / data which has been extracted from one of the generated CDP domain 014 * command methods. 015 * 016 * <BR /><BR /> 017 * A "Domain Command" is any static method generated inside one of the primary browser domain 018 * classes. For example: 019 * 020 * <BR /><BR /> 021 * <B><CODE>{@link Torello.Browser.JavaScriptAPI.RunTime#evaluate}</CODE></B> 022 * 023 * <BR /><BR /> 024 * is a command method declared inside the {@code RunTime} domain class. It accepts 16 input 025 * parameters, most of which are optional. Its corresponding {@code CommandDescriptor} singleton 026 * is created and stored inside the generated command-helper class for that domain: 027 * 028 * <BR /><BR /> 029 * <B><CODE><A HREF='JavaScriptAPI/hilite-files/RunTime$$Commands.java.html'>RunTime$$Commands 030 * </A></CODE></B> 031 * 032 * <BR /><BR /> 033 * The descriptor stores the command name, browser domain, parameter names, CDP type constants, 034 * optional-parameter flags, experimental-parameter flags, and the Java {@link Class} returned by 035 * the command. This metadata is used by {@link CommandBuilder} to construct a {@link Script} 036 * without requiring the user to write long method invocations filled with {@code null} 037 * placeholders. 038 * 039 * @param <T> The Java type returned by the {@link Script} created for this command. This type 040 * parameter allows {@code CommandDescriptor<T>} to work together with 041 * {@link CommandBuilder}{@code <T>}. 042 */ 043public class CommandDescriptor<T> 044 extends AbstractDescriptor 045 implements java.io.Serializable 046{ 047 // ******************************************************************************************** 048 // ******************************************************************************************** 049 // Fields 050 // ******************************************************************************************** 051 // ******************************************************************************************** 052 053 054 /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ 055 protected static final long serialVersionUID = 1L; 056 057 /** The Java {@code Class} returned by the command. */ 058 public final Class<T> returnClass; 059 060 // This package-private field is used by the "CommandBuilder" class's "build()" method 061 private transient java.lang.reflect.Method method = null; 062 063 // This is used below 064 private transient java.lang.reflect.Parameter[] parameters = null; 065 066 067 // ******************************************************************************************** 068 // ******************************************************************************************** 069 // Constructor & toString 070 // ******************************************************************************************** 071 // ******************************************************************************************** 072 073 074 /** 075 * This is a public constructor, but it's primarily intended for internal use. 076 * 077 * <!-- NOTE: Chat-GPT wrote these "Single Line Summaries!" Pretty nifty... --> 078 * 079 * @param name The name of the CDP type described by this descriptor. 080 * @param domain The Browser Domain containing the described CDP type. 081 * @param names Parallel list of CDP property names. 082 * @param types Parallel list of {@link CDPTypes CDP Type Constants}. 083 * @param optionals Parallel list indicating which properties are {@code optional}. 084 * @param experimentals Parallel list indicating which properties are {@code experimental}. 085 * @param returnClass The Java {@code Class} returned by the command. 086 */ 087 public CommandDescriptor( 088 final String name, 089 final Domains domain, 090 final ReadOnlyList<String> names, 091 final ReadOnlyList<Byte> types, 092 final ReadOnlyList<Boolean> optionals, 093 final ReadOnlyList<Boolean> experimentals, 094 final Class<T> returnClass 095 ) 096 { 097 super(name, domain, names, types, optionals, experimentals); 098 this.returnClass = returnClass; 099 } 100 101 /** 102 * Generates a reasonable {@code String} representation of {@code 'this'} instance. 103 * @return A Java {@code String} 104 */ 105 @Override 106 public String toString() 107 { 108 return 109 "CommandDescriptor:\n" + 110 "{\n" + 111 super.toString() + 112 '\n' + 113 I4 + "returnClass: " + this.returnClass.getCanonicalName() + '\n' + 114 "}\n"; 115 } 116 117 118 // ******************************************************************************************** 119 // ******************************************************************************************** 120 // Package-Private Reflection Methods 121 // ******************************************************************************************** 122 // ******************************************************************************************** 123 124 125 // Allows the CommandBuilder to verify if a user has passed a valid class 126 Class<?> getClass(final String name, final int index) 127 { 128 if (this.parameters == null) 129 130 try 131 { this.parameters = this.method().getParameters(); } 132 133 catch (MalformedParametersException e) 134 { throw new CommandDescriptorError("Unable to retrieve method parameters", e); } 135 136 return this.parameters[index].getType(); 137 } 138 139 Method method() 140 { 141 if (this.method != null) return method; 142 143 try 144 { 145 final Method[] methods = 146 this.domain.getDomainClass().getMethods(); 147 148 if (methods == null) 149 throw new CommandDescriptorError("getMethods() returned null"); 150 151 Method found = null; 152 153 for (final Method m : methods) 154 { 155 if (m == null) continue; 156 157 /* 158 System.out.println( 159 "m.getName(): " + m.getName() + '\n' + 160 "this.name: " + this.name + '\n' + 161 "m.getReturnType(): " + m.getReturnType() + '\n' + 162 "this.returnClass: " + this.returnClass.getCanonicalName() + '\n' + 163 '\n' 164 ); 165 */ 166 167 if (m.getName().equals(this.name)) 168 if (m.getReturnType().equals(Script.class)) 169 { 170 if (found != null) throw new CommandDescriptorError( 171 "More than one method matched command name '" + 172 this.name + '\'' 173 ); 174 175 found = m; 176 } 177 } 178 179 if (found == null) throw new CommandDescriptorError 180 ("Unable to locate command method named '" + this.name + '\''); 181 182 if (Modifier.isStatic(found.getModifiers()) == false) throw new CommandDescriptorError 183 ("The reflected command method is not static."); 184 185 this.method = found; 186 } 187 188 catch (SecurityException e) 189 { 190 throw new CommandDescriptorError( 191 "There was an error extracting this command's method from its Domain Class. " + 192 "Please see the getCause() throwable for details.", 193 e 194 ); 195 } 196 197 if (method.getParameterCount() != this.size) throw new CommandDescriptorError( 198 "The number of parameters in the method which was retrieved is " + 199 method.getParameterCount() + ", but the number of parameters listed in the " + 200 "command descriptor is " + this.size + ". These numbers should be equal." 201 ); 202 203 return this.method; 204 } 205 206 private static class CommandDescriptorError extends Error 207 { 208 protected static final long serialVersionUID = 1; 209 210 private CommandDescriptorError(final String message) 211 { super(message); } 212 213 private CommandDescriptorError(final String message, final Throwable cause) 214 { super(message, cause); } 215 } 216}