Clover coverage report - JGAP 3.1
Coverage timestamp: Mo Dez 11 2006 21:16:18 CET
file stats: LOC: 669   Methods: 33
NCLOC: 380   Classes: 1
0 - Aktuelle Promotion 728x090
 
 Source file Conditionals Statements Methods TOTAL
AbstractSupergene.java 55,3% 68,5% 69,7% 64,8%
coverage coverage
 1    /*
 2    * This file is part of JGAP.
 3    *
 4    * JGAP offers a dual license model containing the LGPL as well as the MPL.
 5    *
 6    * For licencing information please see the file license.txt included with JGAP
 7    * or have a look at the top of class org.jgap.Chromosome which representatively
 8    * includes the JGAP license policy applicable for any file delivered with JGAP.
 9    */
 10    package org.jgap.supergenes;
 11   
 12    import java.io.*;
 13    import java.lang.reflect.*;
 14    import java.net.*;
 15    import java.util.*;
 16    import org.jgap.*;
 17   
 18    /**
 19    * Combined implementation of both Supergene and SupergeneValidator.
 20    * A working supergene can be easily created from this class just by
 21    * adding genes and overriding
 22    * {@link org.jgap.supergenes.AbstractSupergene#isValid(Gene [] a_case,
 23    * Supergene a_forSupergene) isValid (Gene [], Supergene)}
 24    * method. For more complex cases, you may need to set your own
 25    * {@link org.jgap.supergenes.Validator Validator}.
 26    *
 27    * @author Audrius Meskauskas
 28    * @since 2.0
 29    */
 30    public abstract class AbstractSupergene
 31    extends BaseGene
 32    implements Supergene, SupergeneValidator {
 33    /** String containing the CVS revision. Read out via reflection!*/
 34    private final static String CVS_REVISION = "$Revision: 1.20 $";
 35   
 36    /**
 37    * This field separates gene class name from
 38    * the gene persistent representation string.
 39    */
 40    public final static String GENE_DELIMITER = "#";
 41   
 42    /**
 43    * Represents the heading delimiter that is used to separate genes in the
 44    * persistent representation of CompositeGene instances.
 45    */
 46    public final static String GENE_DELIMITER_HEADING = "<";
 47   
 48    /**
 49    * Represents the closing delimiter that is used to separate genes in the
 50    * persistent representation of CompositeGene instances.
 51    */
 52    public final static String GENE_DELIMITER_CLOSING = ">";
 53   
 54    /**
 55    * Maximal number of retries for applyMutation and setToRandomValue.
 56    * If the valid supergen cannot be created after this number of iterations,
 57    * the error message is printed and the unchanged instance is returned.
 58    * */
 59    public final static int MAX_RETRIES = 1;
 60   
 61    /**
 62    * Maximal number of notes about immutable genes per
 63    * single gene position
 64    * */
 65    public final static int MAX_IMMUTABLE_GENES = 100000;
 66   
 67    /** Holds the genes of this supergene. */
 68    private Gene[] m_genes;
 69   
 70    /** Set of supergene allele values that cannot mutate. */
 71    private static Set[] m_immutable = new Set[1];
 72   
 73    /**
 74    * @return the array of genes - components of this supergene. The supergene
 75    * components may be supergenes itself
 76    */
 77  5 public Gene[] getGenes() {
 78  5 return m_genes;
 79    }
 80   
 81    /**
 82    * Returns the Gene at the given index (locus) within the Chromosome. The
 83    * first gene is at index zero and the last gene is at the index equal to
 84    * the size of this Chromosome - 1.
 85    *
 86    * This seems to be one of the bottlenecks, so it is declared final.
 87    * I cannot imagine the reason for overriding this trivial single line
 88    * method.
 89    *
 90    * @param a_index the index of the gene value to be returned
 91    * @return the Gene at the given index
 92    */
 93  6 public final Gene geneAt(final int a_index) {
 94  6 return m_genes[a_index];
 95    };
 96   
 97    /**
 98    * Default constructor for dynamic instantiation.
 99    *
 100    * @throws InvalidConfigurationException
 101    *
 102    * @author Klaus Meffert
 103    * @since 3.0
 104    */
 105  0 public AbstractSupergene()
 106    throws InvalidConfigurationException {
 107  0 this(Genotype.getStaticConfiguration(), new Gene[]{});
 108    }
 109   
 110    /**
 111    * Constructor for dynamic instantiation.
 112    *
 113    * @throws InvalidConfigurationException
 114    *
 115    * @author Klaus Meffert
 116    * @since 3.0
 117    */
 118  0 public AbstractSupergene(final Configuration a_config)
 119    throws InvalidConfigurationException {
 120  0 this(a_config, new Gene[]{});
 121    }
 122   
 123    /**
 124    * Constructs abstract supergene with the given gene list.
 125    *
 126    * @param a_conf the configuration to use
 127    * @param a_genes array of genes for this Supergene
 128    * @throws InvalidConfigurationException
 129    */
 130  58 public AbstractSupergene(final Configuration a_conf, final Gene[] a_genes)
 131    throws InvalidConfigurationException {
 132  58 super(a_conf);
 133  58 if (a_genes == null) {
 134  0 throw new RuntimeException("null value for genes not allowed!");
 135    }
 136  58 m_genes = a_genes;
 137    }
 138   
 139    /**
 140    * Test the allele combination of this supergene for validity. This method
 141    * calls isValid for the current gene list.
 142    * @return true only if the supergene allele combination is valid
 143    * or the setValidator (<i>null</i>) has been previously called
 144    */
 145  2 public boolean isValid() {
 146  2 if (m_validator == null) {
 147  1 return true;
 148    }
 149    else {
 150  1 return m_validator.isValid(m_genes, this);
 151    }
 152    }
 153   
 154    /**
 155    * Test the given gene list for validity. The genes must exactly the same
 156    * as inside this supergene.
 157    * At <i>least about 5 % of the randomly
 158    * generated Supergene suparallele values should be valid.</i> If the valid
 159    * combinations represents too small part of all possible combinations,
 160    * it can take too long to find the suitable mutation that does not brake
 161    * a supergene. If you face this problem, try to split the supergene into
 162    * several sub-supergenes.
 163    *
 164    * This method is only called if you have not set any alternative
 165    * validator (including <i>null</i>).
 166    *
 167    * @param a_case ignored here
 168    * @param a_forSupergene ignored here
 169    *
 170    * @return true only if the supergene allele combination is valid
 171    * @throws Error by default. If you do not set external validator,
 172    * you should always override this method
 173    */
 174  1 public boolean isValid(final Gene[] a_case, final Supergene a_forSupergene) {
 175  1 throw new Error("For " + getClass().getName() + ", override "
 176    + " isValid (Gene[], Supergene) or set an"
 177    + " external validator.");
 178    }
 179   
 180    /**
 181    * Creates a new instance of this Supergene class with the same number of
 182    * genes, calling newGene() for each subgene. The class, derived from this
 183    * abstract supergene will be instantiated
 184    * (not the instance of abstractSupergene itself). If the external
 185    * validator is set, the same validator will be set for the new gene.
 186    *
 187    * @return the new Gene
 188    * @throws Error if the instance of <i>this</i> cannot be instantiated
 189    * (for example, if it is not public or the parameterless constructor is
 190    * not provided).
 191    * */
 192  3 protected Gene newGeneInternal() {
 193  3 Gene[] g = new Gene[m_genes.length];
 194  3 for (int i = 0; i < m_genes.length; i++) {
 195  5 g[i] = m_genes[i].newGene();
 196    }
 197  3 try {
 198  3 Constructor constr = getClass().getConstructor(new Class[] {Configuration.class, Gene[].class});
 199  3 AbstractSupergene age =
 200    (AbstractSupergene) constr.newInstance(new Object[] {getConfiguration(), getGenes()});
 201  3 if (m_validator != this) {
 202  1 age.setValidator(m_validator);
 203    }
 204  3 age.m_genes = g;
 205  3 return age;
 206    }
 207    catch (Exception ex) {
 208  0 ex.printStackTrace();
 209  0 throw new Error(
 210    "This should not happen. Is the constructor with parameters "
 211    + "{org.jgap.Configuration, org,jgap,Gene[]} provided for "
 212    + getClass().getName() + "?");
 213    }
 214    }
 215   
 216    /**
 217    * Applies a mutation of a given intensity (percentage) onto the gene
 218    * at the given index. Retries while isValid() returns true for the
 219    * supergene. The method is delegated to the first element ] of the
 220    * gene, indexed by <code>index</code>.
 221    * See org.jgap.supergenes.AbstractSupergene.isValid()
 222    */
 223  0 public void applyMutation(final int a_index, final double a_percentage) {
 224    // Return immediately the current value is found in
 225    // the list of immutable alleles for this position.
 226    // ---------------------------------------------------
 227  0 if (a_index < m_immutable.length) {
 228  0 if (m_immutable[a_index] != null) {
 229  0 synchronized (m_immutable) {
 230  0 if (m_immutable[a_index].contains(this)) {
 231  0 return;
 232    }
 233    }
 234    }
 235    }
 236    // Following commented out because if only very few valid states exist it
 237    // may be that they are not reached within a given number of tries.
 238    // ----------------------------------------------------------------------
 239    // if (!isValid()) {
 240    // throw new Error("Should be valid on entry");
 241    // }
 242  0 Object backup = m_genes[a_index].getAllele();
 243  0 for (int i = 0; i < MAX_RETRIES; i++) {
 244  0 m_genes[a_index].applyMutation(0, a_percentage);
 245  0 if (isValid()) {
 246  0 return;
 247    }
 248    }
 249    // restore the gene as it was
 250  0 m_genes[a_index].setAllele(backup);
 251  0 markImmutable(a_index);
 252    }
 253   
 254    /** @todo: Implement protection against overgrowing of this
 255    * data block.
 256    */
 257  0 private void markImmutable(final int a_index) {
 258  0 synchronized (m_immutable) {
 259  0 if (m_immutable.length <= a_index) {
 260    // Extend the array (double length).
 261    // ---------------------------------
 262  0 Set[] r = new Set[2 * m_immutable.length];
 263  0 System.arraycopy(m_immutable, 0, r, 0, m_immutable.length);
 264  0 m_immutable = r;
 265    }
 266  0 if (m_immutable[a_index] == null) {
 267  0 m_immutable[a_index] = new TreeSet();
 268    }
 269  0 if (m_immutable[a_index].size() < MAX_IMMUTABLE_GENES) {
 270  0 m_immutable[a_index].add(this);
 271    }
 272    }
 273    ;
 274    }
 275   
 276    /**
 277    * Discards all internal caches, ensuring correct repetetive tests
 278    * of performance. Differently from cleanup(), discards also static
 279    * references, that are assumed to be useful for the multiple instances
 280    * of the Supergene.
 281    * Clears the set of the alleles that are known to be immutable.
 282    */
 283  1 public static void reset() {
 284  1 m_immutable = new Set[1];
 285    }
 286   
 287    /**
 288    * Sets the value of this Gene to a random legal value for the
 289    * implementation. It calls setToRandomValue for all subgenes and
 290    * then validates. With a large number of subgenes and low percent of
 291    * valid combinations this may take too long to complete. We think,
 292    * at lease several % of the all possible combintations must be valid.
 293    */
 294  0 public void setToRandomValue(final RandomGenerator a_numberGenerator) {
 295    // set all to random value first
 296  0 for (int i = 0; i < m_genes.length; i++) {
 297  0 m_genes[i].setToRandomValue(a_numberGenerator);
 298    }
 299  0 if (isValid()) {
 300  0 return;
 301    }
 302  0 for (int i = 0; i < MAX_RETRIES; i++) {
 303  0 for (int j = 0; j < m_genes.length; j++) {
 304    // Mutate only one gene at time.
 305    // -----------------------------
 306  0 m_genes[j].setToRandomValue(a_numberGenerator);
 307  0 if (isValid()) {
 308  0 return;
 309    }
 310    }
 311    }
 312    }
 313   
 314    /**
 315    * Sets the allele.
 316    * @param a_superAllele must be an array of objects, size matching the
 317    * number of genes
 318    */
 319  3 public void setAllele(final Object a_superAllele) {
 320  3 if (m_genes.length < 1) {
 321    // Nothing to do
 322  1 return;
 323    }
 324  2 Object[] a = (Object[]) a_superAllele;
 325  2 if (a.length != m_genes.length) {
 326  1 throw new IllegalArgumentException("Record length, " + a.length
 327    + " not equal to "
 328    + m_genes.length);
 329    }
 330  1 for (int i = 0; i < m_genes.length; i++) {
 331  5 m_genes[i].setAllele(a[i]);
 332    }
 333    }
 334   
 335    /**
 336    * Retrieves the allele value represented by this Supergene.
 337    * @return array of objects, each matching the subgene in this Supergene
 338    */
 339  0 public Object getAllele() {
 340  0 Object[] o = new Object[m_genes.length];
 341  0 for (int i = 0; i < m_genes.length; i++) {
 342  0 o[i] = m_genes[i].getAllele();
 343    }
 344  0 return o;
 345    }
 346   
 347    /**
 348    * @return a string representation of the value of this Supergene
 349    * instance, using calls to the Supergene components. Supports other
 350    * (nested) supergenes in this supergene
 351    */
 352  5 public String getPersistentRepresentation() {
 353  5 StringBuffer b = new StringBuffer();
 354    // Write validator:
 355  5 String validator = null;
 356  5 String v_representation = "";
 357  5 SupergeneValidator v = getValidator();
 358  5 if (v == null) {
 359  1 validator = "null";
 360    }
 361    else
 362  4 if (v == this) {
 363  3 validator = "this";
 364    }
 365    else {
 366  1 validator = v.getClass().getName();
 367  1 v_representation = v.getPersistent();
 368    }
 369  5 b.append(GENE_DELIMITER_HEADING);
 370  5 b.append(encode(validator + GENE_DELIMITER + v_representation));
 371  5 b.append(GENE_DELIMITER_CLOSING);
 372    // Write genes:
 373  5 Gene gene;
 374  5 for (int i = 0; i < m_genes.length; i++) {
 375  11 gene = m_genes[i];
 376  11 b.append(GENE_DELIMITER_HEADING);
 377  11 b.append(encode(gene.getClass().getName() + GENE_DELIMITER
 378    + gene.getPersistentRepresentation()));
 379  11 b.append(GENE_DELIMITER_CLOSING);
 380    }
 381  5 return b.toString();
 382    }
 383   
 384    /**
 385    * Sets the value and internal state of this Gene from the string
 386    * representation returned by a previous invocation of the
 387    * getPersistentRepresentation() method.
 388    *
 389    * If the validator is not THIS and not null, a new validator is
 390    * created using Class.forName(..).newInstance.
 391    *
 392    * @param a_representation the string representation retrieved from a
 393    * prior call to the getPersistentRepresentation() method
 394    *
 395    * @throws UnsupportedRepresentationException
 396    *
 397    * @author Audrius Meskauskas
 398    * @since 2.0
 399    */
 400  6 public void setValueFromPersistentRepresentation(String a_representation)
 401    throws UnsupportedRepresentationException {
 402  6 if (a_representation != null) {
 403  5 try {
 404    /// Remove the old content.
 405    // ------------------------
 406  5 List r = split(a_representation);
 407  5 Iterator iter = r.iterator();
 408  5 m_genes = new Gene[r.size() - 1];
 409    // The first member in array is a validator representation.
 410    // --------------------------------------------------------
 411  5 StringTokenizer st;
 412  5 String clas;
 413  5 String representation;
 414  5 String g;
 415  5 Gene gene;
 416  5 String validator = (String) iter.next();
 417  5 setValidator(createValidator(decode(validator)));
 418  5 for (int i = 0; i < m_genes.length; i++) {
 419  11 g = decode( (String) iter.next());
 420  11 st = new StringTokenizer(g, GENE_DELIMITER);
 421  11 if (st.countTokens() != 2)
 422  0 throw new UnsupportedRepresentationException("In " + g + ", " +
 423    "expecting two tokens, separated by " + GENE_DELIMITER);
 424  11 clas = st.nextToken();
 425  11 representation = st.nextToken();
 426  11 gene = createGene(clas, representation);
 427  11 m_genes[i] = gene;
 428    }
 429    }
 430    catch (Exception ex) {
 431  0 ex.printStackTrace();
 432  0 throw new UnsupportedRepresentationException(ex.getCause().
 433    getMessage());
 434    }
 435    }
 436    else {
 437  1 throw new UnsupportedRepresentationException("null value not allowed");
 438    }
 439    }
 440   
 441    /** Create validator from the string representation. */
 442  5 protected SupergeneValidator createValidator(String a_rep) {
 443  5 try {
 444  5 StringTokenizer vo = new StringTokenizer
 445    (a_rep, GENE_DELIMITER, true);
 446  0 if (vo.countTokens() != 2)throw new Error
 447    ("In " + a_rep + ", expecting two tokens, separated by " +
 448    GENE_DELIMITER);
 449  5 String clas = vo.nextToken();
 450  5 SupergeneValidator sv;
 451  5 if (clas.equals("this")) {
 452  3 sv = this;
 453    }
 454  2 else if (clas.equals("null")) {
 455  1 sv = null;
 456    }
 457    else {
 458    // sv = (SupergeneValidator) Class.forName(clas).newInstance();
 459  1 Class svClass = Class.forName(clas);
 460  1 Constructor constr = svClass.getConstructor(new Class[] {Configuration.class});
 461  1 sv = (SupergeneValidator) constr.newInstance(new Object[] {
 462    getConfiguration()});
 463    }
 464  5 if (sv != null) {
 465  4 sv.setFromPersistent(decode(vo.nextToken()));
 466    }
 467  5 return sv;
 468    }
 469    catch (Exception ex) {
 470  0 throw new Error
 471    ("Unable to create validator from '" + a_rep + "' for " +
 472    getClass().getName(), ex);
 473    }
 474    }
 475   
 476    /** Creates a new instance of gene. */
 477  11 protected Gene createGene(String a_geneClassName,
 478    String a_persistentRepresentation)
 479    throws Exception {
 480  11 Class geneClass = Class.forName(a_geneClassName);
 481  11 Constructor constr = geneClass.getConstructor(new Class[] {Configuration.class});
 482  11 Gene gene = (Gene) constr.newInstance(new Object[] {getConfiguration()});
 483  11 gene.setValueFromPersistentRepresentation(a_persistentRepresentation);
 484  11 return gene;
 485    }
 486   
 487    /** Calls cleanup() for each subgene. */
 488  0 public void cleanup() {
 489  0 for (int i = 0; i < m_genes.length; i++) {
 490  0 m_genes[i].cleanup();
 491    }
 492    }
 493   
 494    /**
 495    * @return a string representation of the supergene, providing
 496    * class name and calling toString() for all subgenes.
 497    */
 498  3 public String toString() {
 499  3 StringBuffer b = new StringBuffer();
 500  3 b.append("Supergene " + getClass().getName() + " {");
 501  3 for (int i = 0; i < m_genes.length; i++) {
 502  1 b.append("|");
 503  1 b.append(m_genes[i].toString());
 504  1 b.append("|");
 505    }
 506  3 if (m_validator == null) {
 507  1 b.append(" non validating");
 508    }
 509    else {
 510  2 b.append(" validator: "+m_validator.getClass().getName());
 511    }
 512  3 b.append("}");
 513  3 return b.toString();
 514    }
 515   
 516    /** Returns the number of the genes-components of this supergene. */
 517  3 public int size() {
 518  3 return m_genes.length;
 519    }
 520   
 521    /** Calls compareTo() for all subgenes. The passed parameter must be
 522    * an instance of AbstractSupergene. */
 523  10 public int compareTo(Object o) {
 524  10 AbstractSupergene q = (AbstractSupergene) o;
 525  9 int c = m_genes.length - q.m_genes.length;
 526  9 if (c != 0) {
 527  4 return c;
 528    }
 529  5 for (int i = 0; i < m_genes.length; i++) {
 530  0 c = m_genes[i].compareTo(q.m_genes[i]);
 531  0 if (c != 0) {
 532  0 return c;
 533    }
 534    }
 535  5 if (getClass().equals(o.getClass())) {
 536  3 return 0;
 537    }
 538  2 return getClass().getName().compareTo(o.getClass().getName());
 539    }
 540   
 541    /**
 542    * Calls equals() for each pair of genes. If the supplied object is
 543    * an instance of the different class, returns false. Also, the
 544    * genes are assumed to be different if they have different validator
 545    * classes (or only one of the validators is set to null).
 546    */
 547  14 public boolean equals(Object a_gene) {
 548  14 if (a_gene == null || ! (a_gene.getClass().equals(getClass()))) {
 549  2 return false;
 550    }
 551  12 AbstractSupergene age = (AbstractSupergene) a_gene;
 552  12 if (m_validator != age.m_validator)
 553  9 if (m_validator != null && age.m_immutable != null)
 554  9 if (!m_validator.getClass().equals(age.m_validator.getClass()))
 555  0 return false;
 556  12 return Arrays.equals(m_genes, age.m_genes);
 557    }
 558   
 559    /** Returns sum of hashCode() of the genes-components. */
 560  0 public int hashCode() {
 561  0 int s = 0;
 562  0 for (int i = m_genes.length - 1; i >= 0; i--) {
 563  0 s += m_genes[i].hashCode();
 564    }
 565  0 return s;
 566    }
 567   
 568    /* Encode string, doubling the separators. */
 569  20 protected static final String encode(String a_x) {
 570  20 try {
 571  20 return URLEncoder.encode(a_x, "UTF-8");
 572    }
 573    catch (UnsupportedEncodingException ex) {
 574  0 throw new Error("This should never happen!");
 575    }
 576    }
 577   
 578    /** Decode string, undoubling the separators. */
 579  38 protected static final String decode(String a_x) {
 580  38 try {
 581  38 return URLDecoder.decode(a_x, "UTF-8");
 582    }
 583    catch (UnsupportedEncodingException ex) {
 584  0 throw new Error("This should never happen!");
 585    }
 586    }
 587   
 588    /**
 589    * Splits the string a_x into individual gene representations
 590    * @param a_string the string to split
 591    * @return the elements of the returned array are the
 592    * persistent representation strings of the genes - components
 593    *
 594    * @author Audrius Meskauskas
 595    */
 596  10 protected static final List split(String a_string)
 597    throws UnsupportedRepresentationException {
 598  10 List a = Collections.synchronizedList(new ArrayList());
 599  10 StringTokenizer st = new StringTokenizer
 600    (a_string, GENE_DELIMITER_HEADING + GENE_DELIMITER_CLOSING, true);
 601  10 while (st.hasMoreTokens()) {
 602  34 if (!st.nextToken().equals(GENE_DELIMITER_HEADING)) {
 603  0 throw new UnsupportedRepresentationException
 604    (a_string + " no open tag");
 605    }
 606  34 String n = st.nextToken();
 607  2 if (n.equals(GENE_DELIMITER_CLOSING)) a.add(""); // Empty token
 608    else {
 609  32 a.add(n);
 610  32 if (!st.nextToken().equals(GENE_DELIMITER_CLOSING)) {
 611  0 throw new UnsupportedRepresentationException
 612    (a_string + " no close tag");
 613    }
 614    }
 615    }
 616  10 return a;
 617    }
 618   
 619    /** Append a new gene to the gene array. */
 620  28 public void addGene(Gene a_gene) {
 621  28 Gene[] genes = new Gene[m_genes.length + 1];
 622  28 System.arraycopy(m_genes, 0, genes, 0, m_genes.length);
 623  28 genes[m_genes.length] = a_gene;
 624  28 m_genes = genes;
 625    }
 626   
 627    /**
 628    * Sets an object, responsible for deciding if the Supergene allele
 629    * combination is valid. If it is set to null, no validation is performed
 630    * (all combinations are assumed to be valid). If no validator is
 631    * set, the method <code>isValid (Gene [] ) </code>is called.
 632    */
 633  8 public void setValidator(SupergeneValidator a_validator) {
 634  8 m_validator = a_validator;
 635    }
 636   
 637    /**
 638    * Gets an object, responsible for deciding if the Supergene allele
 639    * combination is valid. If no external validator was set and the
 640    * class uses its own internal validation method, it returns <i>this</i>
 641    */
 642  5 public SupergeneValidator getValidator() {
 643  5 return m_validator;
 644    }
 645   
 646    /** A validator (initially set to <i>this</i> */
 647    protected SupergeneValidator m_validator = this;
 648   
 649    /** {@inheritDoc}
 650    * The default implementation returns an empty string. */
 651  0 public String getPersistent() {
 652  0 return "";
 653    }
 654   
 655    /** {@inheritDoc}
 656    * The default implementation does nothing. */
 657  3 public void setFromPersistent(String a_from) {
 658    }
 659   
 660    /**
 661    * @return not needed for abstract supergene
 662    */
 663  0 public Object getInternalValue() {
 664  0 if (true) {
 665  0 throw new RuntimeException("getInternalValue() called unexpectedly!");
 666    }
 667  0 return null;
 668    }
 669    }