< prev index next >

src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java

Print this page
rev 49851 : 8202184: Reduce time blocking the ClassSpecializer cache creating SpeciesData
Reviewed-by: jrose, psandoz


  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.lang.invoke;
  27 
  28 import jdk.internal.loader.BootLoader;
  29 import jdk.internal.org.objectweb.asm.ClassWriter;
  30 import jdk.internal.org.objectweb.asm.FieldVisitor;
  31 import jdk.internal.org.objectweb.asm.MethodVisitor;
  32 import jdk.internal.vm.annotation.Stable;
  33 import sun.invoke.util.BytecodeName;
  34 
  35 import java.lang.reflect.*;


  36 import java.security.AccessController;
  37 import java.security.PrivilegedAction;
  38 import java.security.ProtectionDomain;
  39 import java.util.*;



  40 import java.util.concurrent.ConcurrentHashMap;
  41 import java.util.concurrent.ConcurrentMap;
  42 import java.util.function.Function;
  43 
  44 import static java.lang.invoke.LambdaForm.*;
  45 import static java.lang.invoke.MethodHandleNatives.Constants.REF_getStatic;
  46 import static java.lang.invoke.MethodHandleNatives.Constants.REF_putStatic;
  47 import static java.lang.invoke.MethodHandleStatics.*;
  48 import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
  49 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  50 
  51 /**
  52  * Class specialization code.
  53  * @param <T> top class under which species classes are created.
  54  * @param <K> key which identifies individual specializations.
  55  * @param <S> species data type.
  56  */
  57 /*non-public*/
  58 abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesData> {
  59     private final Class<T> topClass;
  60     private final Class<K> keyType;
  61     private final Class<S> metaType;
  62     private final MemberName sdAccessor;
  63     private final String sdFieldName;
  64     private final List<MemberName> transformMethods;
  65     private final MethodType baseConstructorType;
  66     private final S topSpecies;
  67     private final ConcurrentMap<K, S> cache = new ConcurrentHashMap<>();
  68     private final Factory factory;
  69     private @Stable boolean topClassIsSuper;
  70 
  71     /** Return the top type mirror, for type {@code T} */
  72     public final Class<T> topClass() { return topClass; }
  73 
  74     /** Return the key type mirror, for type {@code K} */
  75     public final Class<K> keyType() { return keyType; }
  76 
  77     /** Return the species metadata type mirror, for type {@code S} */
  78     public final Class<S> metaType() { return metaType; }
  79 
  80     /** Report the leading arguments (if any) required by every species factory.
  81      * Every species factory adds its own field types as additional arguments,
  82      * but these arguments always come first, in every factory method.
  83      */
  84     protected MethodType baseConstructorType() { return baseConstructorType; }
  85 
  86     /** Return the trivial species for the null sequence of arguments. */
  87     protected final S topSpecies() { return topSpecies; }


  96      * Constructor for this class specializer.
  97      * @param topClass type mirror for T
  98      * @param keyType type mirror for K
  99      * @param metaType type mirror for S
 100      * @param baseConstructorType principal constructor type
 101      * @param sdAccessor the method used to get the speciesData
 102      * @param sdFieldName the name of the species data field, inject the speciesData object
 103      * @param transformMethods optional list of transformMethods
 104      */
 105     protected ClassSpecializer(Class<T> topClass,
 106                                Class<K> keyType,
 107                                Class<S> metaType,
 108                                MethodType baseConstructorType,
 109                                MemberName sdAccessor,
 110                                String sdFieldName,
 111                                List<MemberName> transformMethods) {
 112         this.topClass = topClass;
 113         this.keyType = keyType;
 114         this.metaType = metaType;
 115         this.sdAccessor = sdAccessor;
 116         // FIXME: use List.copyOf once 8177290 is in
 117         this.transformMethods = List.of(transformMethods.toArray(new MemberName[transformMethods.size()]));
 118         this.sdFieldName = sdFieldName;
 119         this.baseConstructorType = baseConstructorType.changeReturnType(void.class);
 120         this.factory = makeFactory();
 121         K tsk = topSpeciesKey();
 122         S topSpecies = null;
 123         if (tsk != null && topSpecies == null) {
 124             // if there is a key, build the top species if needed:
 125             topSpecies = findSpecies(tsk);
 126         }
 127         this.topSpecies = topSpecies;
 128     }
 129 
 130     // Utilities for subclass constructors:
 131     protected static <T> Constructor<T> reflectConstructor(Class<T> defc, Class<?>... ptypes) {
 132         try {
 133             return defc.getDeclaredConstructor(ptypes);
 134         } catch (NoSuchMethodException ex) {
 135             throw newIAE(defc.getName()+"("+MethodType.methodType(void.class, ptypes)+")", ex);
 136         }
 137     }
 138 
 139     protected static Field reflectField(Class<?> defc, String name) {
 140         try {
 141             return defc.getDeclaredField(name);
 142         } catch (NoSuchFieldException ex) {
 143             throw newIAE(defc.getName()+"."+name, ex);
 144         }
 145     }
 146 
 147     private static RuntimeException newIAE(String message, Throwable cause) {
 148         return new IllegalArgumentException(message, cause);
 149     }
 150 
 151     public final S findSpecies(K key) {
 152         S speciesData = cache.computeIfAbsent(key, new Function<>() {
 153             @Override
 154             public S apply(K key1) {
 155                 return factory.loadSpecies(newSpeciesData(key1));
 156             }
 157         });
 158         // Note:  Species instantiation may throw VirtualMachineError because of
 159         // code cache overflow.  If this happens the species bytecode may be
 160         // loaded but not linked to its species metadata (with MH's etc).
 161         // That will cause a throw out of CHM.computeIfAbsent,
 162         // which will shut down the caller thread.
 163         //
 164         // In a latter attempt to get the same species, the already-loaded
 165         // class will be present in the system dictionary, causing an
 166         // error when the species generator tries to reload it.
 167         // We try to detect this case and link the pre-existing code.
 168         //
 169         // Although it would be better to start fresh by loading a new
 170         // copy, we have to salvage the previously loaded but broken code.
 171         // (As an alternative, we might spin a new class with a new name,
 172         // or use the anonymous class mechanism.)
 173         //
 174         // In the end, as long as everybody goes through the same CHM,
 175         // CHM.computeIfAbsent will ensure only one SpeciesData will be set
 176         // successfully on a concrete class if ever.
 177         // The concrete class is published via SpeciesData instance
 178         // returned here only after the class and species data are linked together.
 179         assert(speciesData != null);


























 180         return speciesData;
 181     }
 182 
 183     /**
 184      * Meta-data wrapper for concrete subtypes of the top class.
 185      * Each concrete subtype corresponds to a given sequence of basic field types (LIJFD).
 186      * The fields are immutable; their values are fully specified at object construction.
 187      * Each species supplies an array of getter functions which may be used in lambda forms.
 188      * A concrete value is always constructed from the full tuple of its field values,
 189      * accompanied by the required constructor parameters.
 190      * There *may* also be transforms which cloning a species instance and
 191      * either replace a constructor parameter or add one or more new field values.
 192      * The shortest possible species has zero fields.
 193      * Subtypes are not interrelated among themselves by subtyping, even though
 194      * it would appear that a shorter species could serve as a supertype of a
 195      * longer one which extends it.
 196      */
 197     public abstract class SpeciesData {
 198         // Bootstrapping requires circular relations Class -> SpeciesData -> Class
 199         // Therefore, we need non-final links in the chain.  Use @Stable fields.
 200         private final K key;
 201         private final List<Class<?>> fieldTypes;
 202         @Stable private Class<? extends T> speciesCode;
 203         @Stable private List<MethodHandle> factories;
 204         @Stable private List<MethodHandle> getters;
 205         @Stable private List<LambdaForm.NamedFunction> nominalGetters;
 206         @Stable private final MethodHandle[] transformHelpers = new MethodHandle[transformMethods.size()];
 207 
 208         protected SpeciesData(K key) {
 209             this.key = keyType.cast(Objects.requireNonNull(key));
 210             List<Class<?>> types = deriveFieldTypes(key);
 211             // TODO: List.copyOf
 212             int arity = types.size();
 213             this.fieldTypes = List.of(types.toArray(new Class<?>[arity]));
 214         }
 215 
 216         public final K key() {
 217             return key;
 218         }
 219 
 220         protected final List<Class<?>> fieldTypes() {
 221             return fieldTypes;
 222         }
 223 
 224         protected final int fieldCount() {
 225             return fieldTypes.size();
 226         }
 227 
 228         protected ClassSpecializer<T,K,S> outer() {
 229             return ClassSpecializer.this;
 230         }
 231 
 232         protected final boolean isResolved() {
 233             return speciesCode != null && factories != null && !factories.isEmpty();


 441          */
 442         S loadSpecies(S speciesData) {
 443             String className = speciesData.deriveClassName();
 444             assert(className.indexOf('/') < 0) : className;
 445             Class<?> salvage = null;
 446             try {
 447                 salvage = BootLoader.loadClassOrNull(className);
 448                 if (TRACE_RESOLVE && salvage != null) {
 449                     // Used by jlink species pregeneration plugin, see
 450                     // jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin
 451                     System.out.println("[SPECIES_RESOLVE] " + className + " (salvaged)");
 452                 }
 453             } catch (Error ex) {
 454                 if (TRACE_RESOLVE) {
 455                     System.out.println("[SPECIES_FRESOLVE] " + className + " (Error) " + ex.getMessage());
 456                 }
 457             }
 458             final Class<? extends T> speciesCode;
 459             if (salvage != null) {
 460                 speciesCode = salvage.asSubclass(topClass());
 461                 factory.linkSpeciesDataToCode(speciesData, speciesCode);
 462                 factory.linkCodeToSpeciesData(speciesCode, speciesData, true);
 463             } else {
 464                 // Not pregenerated, generate the class
 465                 try {
 466                     speciesCode = generateConcreteSpeciesCode(className, speciesData);
 467                     if (TRACE_RESOLVE) {
 468                         // Used by jlink species pregeneration plugin, see
 469                         // jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin
 470                         System.out.println("[SPECIES_RESOLVE] " + className + " (generated)");
 471                     }
 472                     // This operation causes a lot of churn:
 473                     linkSpeciesDataToCode(speciesData, speciesCode);
 474                     // This operation commits the relation, but causes little churn:
 475                     linkCodeToSpeciesData(speciesCode, speciesData, false);
 476                 } catch (Error ex) {
 477                     if (TRACE_RESOLVE) {
 478                         System.out.println("[SPECIES_RESOLVE] " + className + " (Error #2)" );
 479                     }
 480                     // We can get here if there is a race condition loading a class.
 481                     // Or maybe we are out of resources.  Back out of the CHM.get and retry.
 482                     throw ex;
 483                 }
 484             }
 485 
 486             if (!speciesData.isResolved()) {
 487                 throw newInternalError("bad species class linkage for " + className + ": " + speciesData);
 488             }
 489             assert(speciesData == factory.loadSpeciesDataFromCode(speciesCode));
 490             return speciesData;
 491         }
 492 
 493         /**
 494          * Generate a concrete subclass of the top class for a given combination of bound types.
 495          *
 496          * A concrete species subclass roughly matches the following schema:
 497          *
 498          * <pre>
 499          * class Species_[[types]] extends [[T]] {
 500          *     final [[S]] speciesData() { return ... }
 501          *     static [[T]] make([[fields]]) { return ... }
 502          *     [[fields]]
 503          *     final [[T]] transform([[args]]) { return ... }
 504          * }
 505          * </pre>
 506          *
 507          * The {@code [[types]]} signature is precisely the key for the species.
 508          *
 509          * The {@code [[fields]]} section consists of one field definition per character in




  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.lang.invoke;
  27 
  28 import jdk.internal.loader.BootLoader;
  29 import jdk.internal.org.objectweb.asm.ClassWriter;
  30 import jdk.internal.org.objectweb.asm.FieldVisitor;
  31 import jdk.internal.org.objectweb.asm.MethodVisitor;
  32 import jdk.internal.vm.annotation.Stable;
  33 import sun.invoke.util.BytecodeName;
  34 
  35 import java.lang.reflect.Constructor;
  36 import java.lang.reflect.Field;
  37 import java.lang.reflect.Modifier;
  38 import java.security.AccessController;
  39 import java.security.PrivilegedAction;
  40 import java.security.ProtectionDomain;
  41 import java.util.ArrayList;
  42 import java.util.Collections;
  43 import java.util.List;
  44 import java.util.Objects;
  45 import java.util.concurrent.ConcurrentHashMap;

  46 import java.util.function.Function;
  47 
  48 import static java.lang.invoke.LambdaForm.*;
  49 import static java.lang.invoke.MethodHandleNatives.Constants.REF_getStatic;
  50 import static java.lang.invoke.MethodHandleNatives.Constants.REF_putStatic;
  51 import static java.lang.invoke.MethodHandleStatics.*;
  52 import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
  53 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  54 
  55 /**
  56  * Class specialization code.
  57  * @param <T> top class under which species classes are created.
  58  * @param <K> key which identifies individual specializations.
  59  * @param <S> species data type.
  60  */
  61 /*non-public*/
  62 abstract class ClassSpecializer<T,K,S extends ClassSpecializer<T,K,S>.SpeciesData> {
  63     private final Class<T> topClass;
  64     private final Class<K> keyType;
  65     private final Class<S> metaType;
  66     private final MemberName sdAccessor;
  67     private final String sdFieldName;
  68     private final List<MemberName> transformMethods;
  69     private final MethodType baseConstructorType;
  70     private final S topSpecies;
  71     private final ConcurrentHashMap<K, S> cache = new ConcurrentHashMap<>();
  72     private final Factory factory;
  73     private @Stable boolean topClassIsSuper;
  74 
  75     /** Return the top type mirror, for type {@code T} */
  76     public final Class<T> topClass() { return topClass; }
  77 
  78     /** Return the key type mirror, for type {@code K} */
  79     public final Class<K> keyType() { return keyType; }
  80 
  81     /** Return the species metadata type mirror, for type {@code S} */
  82     public final Class<S> metaType() { return metaType; }
  83 
  84     /** Report the leading arguments (if any) required by every species factory.
  85      * Every species factory adds its own field types as additional arguments,
  86      * but these arguments always come first, in every factory method.
  87      */
  88     protected MethodType baseConstructorType() { return baseConstructorType; }
  89 
  90     /** Return the trivial species for the null sequence of arguments. */
  91     protected final S topSpecies() { return topSpecies; }


 100      * Constructor for this class specializer.
 101      * @param topClass type mirror for T
 102      * @param keyType type mirror for K
 103      * @param metaType type mirror for S
 104      * @param baseConstructorType principal constructor type
 105      * @param sdAccessor the method used to get the speciesData
 106      * @param sdFieldName the name of the species data field, inject the speciesData object
 107      * @param transformMethods optional list of transformMethods
 108      */
 109     protected ClassSpecializer(Class<T> topClass,
 110                                Class<K> keyType,
 111                                Class<S> metaType,
 112                                MethodType baseConstructorType,
 113                                MemberName sdAccessor,
 114                                String sdFieldName,
 115                                List<MemberName> transformMethods) {
 116         this.topClass = topClass;
 117         this.keyType = keyType;
 118         this.metaType = metaType;
 119         this.sdAccessor = sdAccessor;
 120         this.transformMethods = List.copyOf(transformMethods);

 121         this.sdFieldName = sdFieldName;
 122         this.baseConstructorType = baseConstructorType.changeReturnType(void.class);
 123         this.factory = makeFactory();
 124         K tsk = topSpeciesKey();
 125         S topSpecies = null;
 126         if (tsk != null && topSpecies == null) {
 127             // if there is a key, build the top species if needed:
 128             topSpecies = findSpecies(tsk);
 129         }
 130         this.topSpecies = topSpecies;
 131     }
 132 
 133     // Utilities for subclass constructors:
 134     protected static <T> Constructor<T> reflectConstructor(Class<T> defc, Class<?>... ptypes) {
 135         try {
 136             return defc.getDeclaredConstructor(ptypes);
 137         } catch (NoSuchMethodException ex) {
 138             throw newIAE(defc.getName()+"("+MethodType.methodType(void.class, ptypes)+")", ex);
 139         }
 140     }
 141 
 142     protected static Field reflectField(Class<?> defc, String name) {
 143         try {
 144             return defc.getDeclaredField(name);
 145         } catch (NoSuchFieldException ex) {
 146             throw newIAE(defc.getName()+"."+name, ex);
 147         }
 148     }
 149 
 150     private static RuntimeException newIAE(String message, Throwable cause) {
 151         return new IllegalArgumentException(message, cause);
 152     }
 153 
 154     public final S findSpecies(K key) {






 155         // Note:  Species instantiation may throw VirtualMachineError because of
 156         // code cache overflow.  If this happens the species bytecode may be
 157         // loaded but not linked to its species metadata (with MH's etc).
 158         // That will cause a throw out of CHM.computeIfAbsent,
 159         // which will shut down the caller thread.
 160         //
 161         // In a latter attempt to get the same species, the already-loaded
 162         // class will be present in the system dictionary, causing an
 163         // error when the species generator tries to reload it.
 164         // We try to detect this case and link the pre-existing code.
 165         //
 166         // Although it would be better to start fresh by loading a new
 167         // copy, we have to salvage the previously loaded but broken code.
 168         // (As an alternative, we might spin a new class with a new name,
 169         // or use the anonymous class mechanism.)
 170         //
 171         // In the end, as long as everybody goes through the same CHM,
 172         // CHM.computeIfAbsent will ensure only one SpeciesData will be set
 173         // successfully on a concrete class if ever.
 174         // The concrete class is published via SpeciesData instance
 175         // returned here only after the class and species data are linked together.
 176         S speciesData = cache.computeIfAbsent(key, new Function<>() {
 177             @Override
 178             public S apply(K key1) {
 179                 return newSpeciesData(key1);
 180             }
 181         });
 182         // Separating the creation of a placeholder SpeciesData instance above
 183         // from the loading and linking a real one below ensures we can never
 184         // accidentally call computeIfAbsent recursively.  Replacing rather than
 185         // updating the placeholder is done to ensure safe publication.
 186         if (!speciesData.isResolved()) {
 187             synchronized (speciesData) {
 188                 S existingSpeciesData = cache.get(key);
 189                 if (existingSpeciesData == speciesData) { // won the race
 190                     // create a new SpeciesData...
 191                     speciesData = newSpeciesData(key);
 192                     // load and link it...
 193                     speciesData = factory.loadSpecies(speciesData);
 194                     if (!cache.replace(key, existingSpeciesData, speciesData)) {
 195                         throw newInternalError("Concurrent loadSpecies");
 196                     }
 197                 } else { // lost the race; the retrieved existingSpeciesData is the final
 198                     speciesData = existingSpeciesData;
 199                 }
 200             }
 201         }
 202         assert(speciesData != null && speciesData.isResolved());
 203         return speciesData;
 204     }
 205 
 206     /**
 207      * Meta-data wrapper for concrete subtypes of the top class.
 208      * Each concrete subtype corresponds to a given sequence of basic field types (LIJFD).
 209      * The fields are immutable; their values are fully specified at object construction.
 210      * Each species supplies an array of getter functions which may be used in lambda forms.
 211      * A concrete value is always constructed from the full tuple of its field values,
 212      * accompanied by the required constructor parameters.
 213      * There *may* also be transforms which cloning a species instance and
 214      * either replace a constructor parameter or add one or more new field values.
 215      * The shortest possible species has zero fields.
 216      * Subtypes are not interrelated among themselves by subtyping, even though
 217      * it would appear that a shorter species could serve as a supertype of a
 218      * longer one which extends it.
 219      */
 220     public abstract class SpeciesData {
 221         // Bootstrapping requires circular relations Class -> SpeciesData -> Class
 222         // Therefore, we need non-final links in the chain.  Use @Stable fields.
 223         private final K key;
 224         private final List<Class<?>> fieldTypes;
 225         @Stable private Class<? extends T> speciesCode;
 226         @Stable private List<MethodHandle> factories;
 227         @Stable private List<MethodHandle> getters;
 228         @Stable private List<LambdaForm.NamedFunction> nominalGetters;
 229         @Stable private final MethodHandle[] transformHelpers = new MethodHandle[transformMethods.size()];
 230 
 231         protected SpeciesData(K key) {
 232             this.key = keyType.cast(Objects.requireNonNull(key));
 233             List<Class<?>> types = deriveFieldTypes(key);
 234             this.fieldTypes = List.copyOf(types);


 235         }
 236 
 237         public final K key() {
 238             return key;
 239         }
 240 
 241         protected final List<Class<?>> fieldTypes() {
 242             return fieldTypes;
 243         }
 244 
 245         protected final int fieldCount() {
 246             return fieldTypes.size();
 247         }
 248 
 249         protected ClassSpecializer<T,K,S> outer() {
 250             return ClassSpecializer.this;
 251         }
 252 
 253         protected final boolean isResolved() {
 254             return speciesCode != null && factories != null && !factories.isEmpty();


 462          */
 463         S loadSpecies(S speciesData) {
 464             String className = speciesData.deriveClassName();
 465             assert(className.indexOf('/') < 0) : className;
 466             Class<?> salvage = null;
 467             try {
 468                 salvage = BootLoader.loadClassOrNull(className);
 469                 if (TRACE_RESOLVE && salvage != null) {
 470                     // Used by jlink species pregeneration plugin, see
 471                     // jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin
 472                     System.out.println("[SPECIES_RESOLVE] " + className + " (salvaged)");
 473                 }
 474             } catch (Error ex) {
 475                 if (TRACE_RESOLVE) {
 476                     System.out.println("[SPECIES_FRESOLVE] " + className + " (Error) " + ex.getMessage());
 477                 }
 478             }
 479             final Class<? extends T> speciesCode;
 480             if (salvage != null) {
 481                 speciesCode = salvage.asSubclass(topClass());
 482                 linkSpeciesDataToCode(speciesData, speciesCode);
 483                 linkCodeToSpeciesData(speciesCode, speciesData, true);
 484             } else {
 485                 // Not pregenerated, generate the class
 486                 try {
 487                     speciesCode = generateConcreteSpeciesCode(className, speciesData);
 488                     if (TRACE_RESOLVE) {
 489                         // Used by jlink species pregeneration plugin, see
 490                         // jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin
 491                         System.out.println("[SPECIES_RESOLVE] " + className + " (generated)");
 492                     }
 493                     // This operation causes a lot of churn:
 494                     linkSpeciesDataToCode(speciesData, speciesCode);
 495                     // This operation commits the relation, but causes little churn:
 496                     linkCodeToSpeciesData(speciesCode, speciesData, false);
 497                 } catch (Error ex) {
 498                     if (TRACE_RESOLVE) {
 499                         System.out.println("[SPECIES_RESOLVE] " + className + " (Error #2)" );
 500                     }
 501                     // We can get here if there is a race condition loading a class.
 502                     // Or maybe we are out of resources.  Back out of the CHM.get and retry.
 503                     throw ex;
 504                 }
 505             }
 506 
 507             if (!speciesData.isResolved()) {
 508                 throw newInternalError("bad species class linkage for " + className + ": " + speciesData);
 509             }
 510             assert(speciesData == loadSpeciesDataFromCode(speciesCode));
 511             return speciesData;
 512         }
 513 
 514         /**
 515          * Generate a concrete subclass of the top class for a given combination of bound types.
 516          *
 517          * A concrete species subclass roughly matches the following schema:
 518          *
 519          * <pre>
 520          * class Species_[[types]] extends [[T]] {
 521          *     final [[S]] speciesData() { return ... }
 522          *     static [[T]] make([[fields]]) { return ... }
 523          *     [[fields]]
 524          *     final [[T]] transform([[args]]) { return ... }
 525          * }
 526          * </pre>
 527          *
 528          * The {@code [[types]]} signature is precisely the key for the species.
 529          *
 530          * The {@code [[fields]]} section consists of one field definition per character in


< prev index next >