KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jgap > Population


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;
11
12 import java.io.*;
13 import java.lang.reflect.*;
14 import java.util.*;
15 import org.jgap.util.*;
16 import java.net.*;
17
18 /**
19  * List of chromosomes held in the Genotype (or possibly later in the
20  * Configuration object).
21  *
22  * @author Klaus Meffert
23  * @since 2.0
24  */

25 public class Population
26     implements Serializable, ICloneable, IPersistentRepresentation {
27   /** String containing the CVS revision. Read out via reflection!*/
28   private static final String JavaDoc CVS_REVISION = "$Revision: 1.56 $";
29
30   /**
31    * The array of Chromosomes that makeup the Genotype's population.
32    */

33   private List m_chromosomes;
34
35   /**
36    * The fittest Chromosome of the population.
37    */

38   private IChromosome m_fittestChromosome;
39
40   /**
41    * Indicates whether at least one of the chromosomes has been changed
42    * (deleted, added, modified).
43    */

44   private boolean m_changed;
45
46   /**
47    * Indicates that the list of Chromosomes has been sorted.
48    */

49   private boolean m_sorted;
50
51   private
52   /*transient*/ Configuration m_config;
53
54   public final static String JavaDoc CHROM_DELIMITER = "~";
55
56   /**
57    * Represents the heading delimiter that is used to separate chromosomes in
58    * the persistent representation.
59    */

60   public final static String JavaDoc CHROM_DELIMITER_HEADING = "[";
61
62   /**
63    * Represents the closing delimiter that is used to separate chromosomes in
64    * the persistent representation.
65    */

66   public final static String JavaDoc CHROM_DELIMITER_CLOSING = "]";
67
68   /*
69    *
70    * @author Klaus Meffert
71    * @since 3.0
72    */

73   public Population(final Configuration a_config)
74       throws InvalidConfigurationException {
75     this(a_config, 100);
76   }
77
78   /*
79    * Constructs the Population from a list of Chromosomes. Does not use cloning!
80    *
81    * @param a_chromosomes the Chromosome's to be used for building the
82    * Population
83    *
84    * @author Klaus Meffert
85    * @since 2.0
86    */

87   public Population(final Configuration a_config,
88                     final IChromosome[] a_chromosomes)
89       throws InvalidConfigurationException {
90     this(a_config, a_chromosomes.length);
91     synchronized (m_chromosomes) {
92       for (int i = 0; i < a_chromosomes.length; i++) {
93         // In here we could test for null chromosomes, but this is skipped
94
// because of performance issues (although this may seem idiotic...)
95
m_chromosomes.add(a_chromosomes[i]);
96       }
97     }
98     setChanged(true);
99   }
100
101   /*
102    * Constructs the Population from a single Chromosome. Does not use cloning!
103
104    * @param a_chromosome the Chromosome to be used for building the Population
105    *
106    * @author Klaus Meffert
107    * @since 3.2
108    */

109   public Population(final Configuration a_config,
110                     final IChromosome a_chromosome)
111       throws InvalidConfigurationException {
112     this(a_config, 1);
113     if (a_chromosome == null) {
114       throw new IllegalArgumentException JavaDoc("Chromosome passed must not be null!");
115     }
116     synchronized (m_chromosomes) {
117       m_chromosomes.add(a_chromosome);
118     }
119     setChanged(true);
120   }
121
122   /*
123    * Constructs an empty Population with the given initial size.
124
125    * @param a_size the initial size of the empty Population. The initial size
126    * is not fix, it is just for optimized list creation.
127    *
128    * @author Klaus Meffert
129    * @since 2.0
130    */

131   public Population(final Configuration a_config, final int a_size)
132       throws InvalidConfigurationException {
133     if (a_config == null) {
134       throw new InvalidConfigurationException("Configuration must not be null!");
135     }
136     m_config = a_config;
137     // Use a synchronized list (important for distributed computing!)
138
m_chromosomes = new Vector(a_size);
139     setChanged(true);
140   }
141
142   /*
143    * Constructs an empty Population with initial array size 100.
144    *
145    * @author Klaus Meffert
146    * @since 2.0
147    */

148   public Population()
149       throws InvalidConfigurationException {
150     this(Genotype.getStaticConfiguration());
151   }
152
153   public Configuration getConfiguration() {
154     return m_config;
155   }
156
157   /**
158    * Adds a Chromosome to this Population. Does nothing when given null.
159    *
160    * @param a_toAdd the Chromosome to add
161    *
162    * @author Klaus Meffert
163    * @since 2.0
164    */

165   public void addChromosome(final IChromosome a_toAdd) {
166     if (a_toAdd != null) {
167       synchronized (m_chromosomes) {
168         m_chromosomes.add(a_toAdd);
169       }
170       setChanged(true);
171     }
172   }
173
174   /**
175    * Adds all the Chromosomes in the given Population. Does nothing on null or
176    * an empty Population.
177    *
178    * @param a_population the Population to add
179    *
180    * @author Klaus Meffert
181    * @since 2.0
182    */

183   public void addChromosomes(final Population a_population) {
184     if (a_population != null) {
185       synchronized (m_chromosomes) {
186         m_chromosomes.addAll(a_population.getChromosomes());
187       }
188       // The following would do the same:
189
// if (a_population.getChromosomes() != null) {
190
// int size = a_population.getChromosomes().size();
191
// for (int i = 0; i < size; i++) {
192
// IChromosome chrom = a_population.getChromosome(i);
193
// m_chromosomes.add(chrom);
194
// }
195
// }
196
setChanged(true);
197     }
198   }
199
200   /**
201    * Replaces all chromosomes in the population with the give list of
202    * chromosomes.
203    *
204    * @param a_chromosomes the chromosomes to make the population up from
205    *
206    * @author Klaus Meffert
207    */

208   public void setChromosomes(final List a_chromosomes) {
209     synchronized (m_chromosomes) {
210       m_chromosomes = a_chromosomes;
211     }
212     setChanged(true);
213   }
214
215   /**
216    * Sets in the given Chromosome on the given index in the list of chromosomes.
217    * If the given index is exceeding the list by one, the chromosome is
218    * appended.
219    *
220    * @param a_index the index to set the Chromosome in
221    * @param a_chromosome the Chromosome to be set
222    *
223    * @author Klaus Meffert
224    * @since 2.0
225    */

226   public void setChromosome(final int a_index, final IChromosome a_chromosome) {
227     if (m_chromosomes.size() == a_index) {
228       addChromosome(a_chromosome);
229     }
230     else {
231       synchronized (m_chromosomes) {
232         m_chromosomes.set(a_index, a_chromosome);
233       }
234       setChanged(true);
235     }
236   }
237
238   /**
239    * @return the list of Chromosome's in the Population. Don't modify the
240    * retrieved list by using clear(), remove(int) etc. If you do so, you need to
241    * call setChanged(true)
242    *
243    * @author Klaus Meffert
244    * @since 2.0
245    */

246   public List getChromosomes() {
247     return m_chromosomes;
248   }
249
250   /**
251    * @param a_index the index of the Chromosome to be returned
252    * @return Chromosome at given index in the Population
253    *
254    * @author Klaus Meffert
255    * @since 2.0
256    */

257   public IChromosome getChromosome(final int a_index) {
258     return (IChromosome) m_chromosomes.get(a_index);
259   }
260
261   /**
262    * @return number of Chromosome's in the Population
263    *
264    * @author Klaus Meffert
265    * @since 2.0
266    */

267   public int size() {
268     return m_chromosomes.size();
269   }
270
271   /**
272    * @return Iterator for the Chromosome list in the Population. Please be aware
273    * that using remove() forces you to call setChanged(true)
274    *
275    * @author Klaus Meffert
276    * @since 2.0
277    */

278   public Iterator iterator() {
279     return m_chromosomes.iterator();
280   }
281
282   /**
283    * @return the Population converted into a list of Chromosome's
284    *
285    * @author Klaus Meffert, Dan Clark
286    * @since 2.0
287    */

288   public IChromosome[] toChromosomes() {
289     return (IChromosome[]) m_chromosomes.toArray(
290         new IChromosome[m_chromosomes.size()]);
291   }
292
293   /**
294    * Determines the fittest Chromosome in the Population (the one with the
295    * highest fitness value) and memorizes it. This is an optimized version
296    * compared to calling determineFittesChromosomes(1).
297    *
298    * @return the fittest Chromosome of the Population
299    *
300    * @author Klaus Meffert
301    * @since 2.0
302    */

303   public IChromosome determineFittestChromosome() {
304     if (!m_changed && m_fittestChromosome != null) {
305       return m_fittestChromosome;
306     }
307     Iterator it = m_chromosomes.iterator();
308     FitnessEvaluator evaluator = getConfiguration().getFitnessEvaluator();
309     double bestFitness;
310     if (evaluator.isFitter(2.0d, 1.0d)) {
311       bestFitness = -1.0d;
312     }
313     else {
314       bestFitness = Double.MAX_VALUE;
315     }
316     double fitness;
317     while (it.hasNext()) {
318       IChromosome chrom = (IChromosome) it.next();
319       fitness = chrom.getFitnessValue();
320       if (evaluator.isFitter(fitness, bestFitness)
321           || m_fittestChromosome == null) {
322         m_fittestChromosome = chrom;
323         bestFitness = fitness;
324       }
325     }
326     setChanged(false);
327     return m_fittestChromosome;
328   }
329
330   /**
331    * Determines the fittest Chromosome in the population (the one with the
332    * highest fitness value) within the given indices and memorizes it. This is
333    * an optimized version compared to calling determineFittesChromosomes(1).
334    *
335    * @param a_startIndex index to begin the evaluation with
336    * @param a_endIndex index to end the evaluation with
337    * @return the fittest Chromosome of the population within the given indices
338    *
339    * @author Klaus Meffert
340    * @since 3.0
341    */

342   public IChromosome determineFittestChromosome(int a_startIndex,
343       int a_endIndex) {
344     double bestFitness = -1.0d;
345     FitnessEvaluator evaluator = getConfiguration().getFitnessEvaluator();
346     double fitness;
347     int startIndex = Math.max(0, a_startIndex);
348     int endIndex = Math.min(m_chromosomes.size() - 1, a_endIndex);
349     for (int i = startIndex; i < endIndex; i++) {
350       IChromosome chrom = (IChromosome) m_chromosomes.get(i);
351       fitness = chrom.getFitnessValue();
352       if (evaluator.isFitter(fitness, bestFitness)
353           || m_fittestChromosome == null) {
354         m_fittestChromosome = chrom;
355         bestFitness = fitness;
356       }
357     }
358     return m_fittestChromosome;
359   }
360
361   /**
362    * Mark that for the population the fittest chromosome may have changed.
363    *
364    * @param a_changed true: population's fittest chromosome may have changed,
365    * false: fittest chromosome evaluated earlier is still valid
366    *
367    * @author Klaus Meffert
368    * @since 2.2
369    */

370   protected void setChanged(final boolean a_changed) {
371     m_changed = a_changed;
372     setSorted(false);
373   }
374
375   /**
376    * @return true: population's chromosomes (maybe) were changed,
377    * false: not changed for sure
378    *
379    * @since 2.6
380    */

381   public boolean isChanged() {
382     return m_changed;
383   }
384
385   /**
386    * Mark the population as sorted.
387    *
388    * @param a_sorted true: mark population as sorted
389    *
390    * @author Klaus Meffert
391    * @since 2.6
392    */

393   protected void setSorted(final boolean a_sorted) {
394     m_sorted = a_sorted;
395   }
396
397   /**
398    * Determines whether the given chromosome is contained within the population.
399    * @param a_chromosome the chromosome to check
400    * @return true: chromosome contained within population
401    *
402    * @author Klaus Meffert
403    * @since 2.1
404    */

405   public boolean contains(final IChromosome a_chromosome) {
406     return m_chromosomes.contains(a_chromosome);
407   }
408
409   /**
410    * Removes a chromosome in the list at the given index. Method has package
411    * visibility to signal that this is a method not to be used outside the
412    * JGAP kernel under normal circumstances.
413    *
414    * @param a_index index of chromosome to be removed in list
415    * @return removed Chromosome
416    *
417    * @author Klaus Meffert
418    * @since 2.4
419    */

420   IChromosome removeChromosome(final int a_index) {
421     if (a_index < 0 || a_index >= size()) {
422       throw new IllegalArgumentException JavaDoc("Index must be within bounds!");
423     }
424     setChanged(true);
425     return (IChromosome) m_chromosomes.remove(a_index);
426   }
427
428   /**
429    * Sorts the Chromosome list and returns the fittest n Chromosomes in
430    * the population.
431    *
432    * @param a_numberOfChromosomes number of top performer chromosomes to be
433    * returned
434    * @return list of the fittest n Chromosomes of the population, or the fittest
435    * x Chromosomes with x = number of chromosomes in case n > x.
436    *
437    * @author Charles Kevin Hill
438    * @since 2.4
439    */

440   public List determineFittestChromosomes(final int a_numberOfChromosomes) {
441     int numberOfChromosomes = Math.min(a_numberOfChromosomes,
442                                        getChromosomes().size());
443     if (numberOfChromosomes <= 0) {
444       return null;
445     }
446     if (!m_changed && m_sorted) {
447       return getChromosomes().subList(0, numberOfChromosomes);
448     }
449     // Sort the list of chromosomes using the fitness comparator
450
sortByFitness();
451     // Return the top n chromosomes
452
return getChromosomes().subList(0, numberOfChromosomes);
453   }
454
455   /**
456    * Sorts the chromosomes within the population according to their fitness
457    * value using ChromosomFitnessComparator. The fittest chromosome is then
458    * at index 0.
459    *
460    * @author Klaus Meffert
461    * @since 2.6
462    */

463   public void sortByFitness() {
464     // The following construction could be cached but wrt that the
465
// evaluator registered with the configuration could change
466
// --> Don't cache it!
467
sort(new ChromosomeFitnessComparator(getConfiguration().
468         getFitnessEvaluator()));
469     setChanged(false);
470     setSorted(true);
471     m_fittestChromosome = (IChromosome) m_chromosomes.get(0);
472   }
473
474   /**
475    * Sorts the chromosomes within the population utilzing the given comparator.
476    *
477    * @param a_comparator the comparator to utilize for sorting
478    *
479    * @author Klaus Meffert
480    * @since 2.6
481    */

482   protected void sort(Comparator a_comparator) {
483     Collections.sort(getChromosomes(), a_comparator);
484   }
485
486   /**
487    * Returns the genotype of the population, i.e. the list of genes in the
488    * Population.
489    *
490    * @param a_resolveCompositeGenes true: split encountered CompositeGenes
491    * into their single (atomic) genes
492    * @return genotype of the population
493    *
494    * @author Klaus Meffert
495    * @since 2.3
496    */

497   public List getGenome(final boolean a_resolveCompositeGenes) {
498     List result = new Vector();
499     List chroms = getChromosomes();
500     int len = chroms.size();
501     for (int i = 0; i < len; i++) {
502       IChromosome chrom = (IChromosome) chroms.get(i);
503       Gene[] genes = chrom.getGenes();
504       int len2 = genes.length;
505       for (int j = 0; j < len2; j++) {
506         Gene gene = genes[j];
507         if (a_resolveCompositeGenes && gene instanceof ICompositeGene) {
508           addCompositeGene(result, (ICompositeGene) gene);
509         }
510         else {
511           addAtomicGene(result, gene);
512         }
513       }
514     }
515     return result;
516   }
517
518   /**
519    * Adds all the genes of a CompositeGene to a result list.<p>
520    * Note: Method calls itself recursively.
521    *
522    * @param a_result the list to add to
523    * @param a_gene the gene to start with
524    *
525    * @author Klaus Meffert
526    * @since 2.3
527    */

528   private void addCompositeGene(final List a_result, final Gene a_gene) {
529     if (a_gene instanceof ICompositeGene) {
530       int len = a_gene.size();
531       for (int i = 0; i < len; i++) {
532         addCompositeGene(a_result, ( (ICompositeGene) a_gene).geneAt(i));
533       }
534     }
535     else {
536       addAtomicGene(a_result, a_gene);
537     }
538   }
539
540   /**
541    * Helper method for addCompositeGene.
542    *
543    * @param a_result List
544    * @param a_gene Gene
545    *
546    * @author Klaus Meffert
547    * @since 2.3
548    */

549   private void addAtomicGene(final List a_result, final Gene a_gene) {
550     a_result.add(a_gene);
551   }
552
553   public boolean isSorted() {
554     return m_sorted;
555   }
556
557   /**
558    * The equals-method.
559    *
560    * @param a_pop the population instance to compare with
561    * @return true: given object equal to comparing one
562    *
563    * @author Klaus Meffert
564    * @since 2.6
565    */

566   public boolean equals(Object JavaDoc a_pop) {
567     try {
568       return compareTo(a_pop) == 0;
569     } catch (ClassCastException JavaDoc e) {
570       // If the other object isn't an Population instance
571
// then we're not equal.
572
// ------------------------------------------------
573
return false;
574     }
575   }
576
577   /**
578    * This method is not producing symmetric results as -1 is more often returned
579    * than 1 (see description of return value).
580    *
581    * @param a_pop the other population to compare
582    * @return 1: a_pop is null or having fewer chromosomes or equal number
583    * of chromosomes but at least one not contained. 0: both populations
584    * containing exactly the same chromosomes. -1: this population contains fewer
585    * chromosomes than a_pop
586    *
587    * @author Klaus Meffert
588    * @since 2.6
589    */

590   public int compareTo(Object JavaDoc a_pop) {
591     Population other = (Population) a_pop;
592     if (a_pop == null) {
593       return 1;
594     }
595     int size1 = size();
596     int size2 = other.size();
597     if (size1 != size2) {
598       if (size1 < size2) {
599         return -1;
600       }
601       else {
602         return 1;
603       }
604     }
605     List chroms2 = other.getChromosomes();
606     for (int i = 0; i < size1; i++) {
607       if (!chroms2.contains(m_chromosomes.get(i))) {
608         return 1;
609       }
610     }
611     return 0;
612   }
613
614   /**
615    * @return deeply cloned instance of this instance
616    *
617    * @author Klaus Meffert
618    * @since 3.2
619    */

620   public Object JavaDoc clone() {
621     try {
622       Population result = new Population(m_config);
623       // Precautiously set changed to true in case cloning is not 1:1
624
// ------------------------------------------------------------
625
result.m_changed = true;
626       result.m_sorted = false;
627       result.m_fittestChromosome = m_fittestChromosome;
628       int size = m_chromosomes.size();
629       for (int i = 0; i < size; i++) {
630         IChromosome chrom = (IChromosome) m_chromosomes.get(i);
631         result.addChromosome( (IChromosome) chrom.clone());
632       }
633       return result;
634     } catch (Exception JavaDoc ex) {
635       throw new CloneException(ex);
636     }
637   }
638
639   /**
640    * Clears the list of chromosomes. Normally, this should not be necessary.
641    * But especially in distributed computing, a fresh population has to be
642    * provided sometimes.
643    *
644    * @author Klaus Meffert
645    * @since 3.2
646    */

647   public void clear() {
648     m_chromosomes.clear();
649     m_changed = true;
650     m_sorted = true;
651     m_fittestChromosome = null;
652   }
653
654   /**
655    * Returns a persistent representation of this chromosome, see interface Gene
656    * for description. Similar to CompositeGene's routine. But does not include
657    * all information of the chromosome (yet).
658    *
659    * @return string representation of this Chromosome's relevant parts of its
660    * current state
661    * @throws UnsupportedOperationException
662    *
663    * @author Klaus Meffert
664    * @since 3.2
665    */

666   public String JavaDoc getPersistentRepresentation() {
667     StringBuffer JavaDoc b = new StringBuffer JavaDoc();
668     // Persist the chromosomes.
669
// ------------------------
670
IChromosome chrom;
671     for (int i = 0; i < m_chromosomes.size(); i++) {
672       chrom = (IChromosome) m_chromosomes.get(i);
673       if (! (chrom instanceof IPersistentRepresentation)) {
674         throw new RuntimeException JavaDoc("Population contains a chromosome of type "
675                                    + chrom.getClass().getName()
676                                    + " which does not implement"
677                                    + " IPersistentRepresentation!");
678       }
679       b.append(CHROM_DELIMITER_HEADING);
680       try {
681         b.append(URLEncoder.encode(chrom.getClass().getName()
682                                    + CHROM_DELIMITER
683                                    + ( (IPersistentRepresentation) chrom).
684                                    getPersistentRepresentation()
685                                    , "UTF-8"));
686       } catch (UnsupportedEncodingException uex) {
687         throw new RuntimeException JavaDoc("UTF-8 should always be supported!", uex);
688       }
689       b.append(CHROM_DELIMITER_CLOSING);
690     }
691     return b.toString();
692   }
693
694   /**
695    * Counterpart of getPersistentRepresentation.
696    *
697    * @param a_representation the string representation retrieved from a prior
698    * call to the getPersistentRepresentation() method
699    *
700    * @throws UnsupportedRepresentationException
701    *
702    * @author Klaus Meffert
703    * @since 3.2
704    */

705   public void setValueFromPersistentRepresentation(String JavaDoc a_representation)
706       throws UnsupportedRepresentationException {
707     if (a_representation != null) {
708       try {
709         List r = split(a_representation);
710         String JavaDoc g;
711         m_chromosomes = new Vector();
712         // Obtain the chromosomes.
713
// -----------------------
714
Iterator iter = r.iterator();
715         StringTokenizer st;
716         String JavaDoc clas;
717         String JavaDoc representation;
718         IChromosome chrom;
719         while (iter.hasNext()) {
720           g = URLDecoder.decode( (String JavaDoc) iter.next(), "UTF-8");
721           st = new StringTokenizer(g, CHROM_DELIMITER);
722           if (st.countTokens() != 2)
723             throw new UnsupportedRepresentationException("In " + g + ", " +
724                 "expecting two tokens, separated by " + CHROM_DELIMITER);
725           clas = st.nextToken();
726           representation = st.nextToken();
727           chrom = createChromosome(clas, representation);
728           m_chromosomes.add(chrom);
729         }
730         setChanged(true);
731       } catch (Exception JavaDoc ex) {
732         throw new UnsupportedRepresentationException(ex.toString());
733       }
734     }
735   }
736
737   /**
738    * Creates a new Chromosome instance.<p>
739    * Taken and adapted from CompositeGene.
740    *
741    * @param a_chromClassName name of the Chromosome class
742    * @param a_persistentRepresentation persistent representation of the
743    * Chromosome to create (could be obtained via getPersistentRepresentation)
744    *
745    * @return newly created Chromosome
746    * @throws Exception
747    *
748    * @author Klaus Meffert
749    * @since 3.2
750    */

751   protected IChromosome createChromosome(String JavaDoc a_chromClassName,
752                             String JavaDoc a_persistentRepresentation)
753       throws Exception JavaDoc {
754     Class JavaDoc chromClass = Class.forName(a_chromClassName);
755     Constructor constr = chromClass.getConstructor(new Class JavaDoc[] {Configuration.class});
756     IChromosome chrom = (IChromosome) constr.newInstance(new Object JavaDoc[] {
757         getConfiguration()});
758     ( (IPersistentRepresentation) chrom).setValueFromPersistentRepresentation(
759         a_persistentRepresentation);
760     return chrom;
761   }
762
763   /**
764    * Splits the input a_string into individual chromosome representations.<p>
765    * Taken and adapted from CompositeGene.
766    *
767    * @param a_string the string to split
768    * @return the elements of the returned array are the persistent
769    * representation strings of the population's components
770    * @throws UnsupportedRepresentationException
771    *
772    * @author Klaus Meffert
773    * @since 3.2
774    */

775   protected static final List split(String JavaDoc a_string)
776       throws UnsupportedRepresentationException {
777     List a = Collections.synchronizedList(new ArrayList());
778     // No Header data.
779
// ---------------
780

781     // Chromosome data.
782
// ----------------
783
StringTokenizer st = new StringTokenizer
784         (a_string, CHROM_DELIMITER_HEADING + CHROM_DELIMITER_CLOSING, true);
785     while (st.hasMoreTokens()) {
786       if (!st.nextToken().equals(CHROM_DELIMITER_HEADING)) {
787         throw new UnsupportedRepresentationException(a_string + " no open tag");
788       }
789       String JavaDoc n = st.nextToken();
790       if (n.equals(CHROM_DELIMITER_CLOSING)) {
791         a.add(""); /* Empty token */
792       }
793       else {
794         a.add(n);
795         if (!st.nextToken().equals(CHROM_DELIMITER_CLOSING)) {
796           throw new UnsupportedRepresentationException
797               (a_string + " no close tag");
798         }
799       }
800     }
801     return a;
802   }
803
804 }
805
Popular Tags