upstream version 1.2.2
[debian/freetts] / com / sun / speech / freetts / VoiceManager.java
1 /**
2  * Copyright 2003 Sun Microsystems, Inc.
3  * 
4  * See the file "license.terms" for information on usage and
5  * redistribution of this file, and for a DISCLAIMER OF ALL 
6  * WARRANTIES.
7  */
8 package com.sun.speech.freetts;
9
10 import java.io.BufferedReader;
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.FileNotFoundException;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.io.InputStreamReader;
17 import java.net.JarURLConnection;
18 import java.net.MalformedURLException;
19 import java.net.URI;
20 import java.net.URL;
21 import java.net.URLClassLoader;
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.jar.Attributes;
25
26 /**
27  * Provides access to voices for all of FreeTTS. There is only one instance of
28  * the VoiceManager.
29  * 
30  * Each call to getVoices() creates a new instance of each voice.
31  * 
32  * @see Voice
33  * @see VoiceDirectory
34  */
35 public class VoiceManager {
36
37     private static final VoiceManager INSTANCE;
38
39     private static final String PATH_SEPARATOR ;
40
41     /**
42      * we only want one class loader, otherwise the static information for
43      * loaded classes would be duplicated for each class loader
44      */
45     private static final DynamicClassLoader classLoader;
46
47     static {
48         PATH_SEPARATOR = System.getProperty("path.separator");
49         INSTANCE = new VoiceManager();
50         final ClassLoader parent = VoiceManager.class.getClassLoader();
51         classLoader = new DynamicClassLoader(new URL[0], parent);
52     }
53
54     /**
55      * Do not allow creation from outside.
56      */
57     private VoiceManager() {
58     }
59
60     /**
61      * Gets the instance of the VoiceManager
62      * 
63      * @return a VoiceManager
64      */
65     public static VoiceManager getInstance() {
66         return INSTANCE;
67     }
68
69     /**
70      * Provide an array of all voices available to FreeTTS.
71      * 
72      * First, if the "freetts.voices" property is set, it is assumed to be a
73      * comma-separated list of VoiceDirectory classnames (e.g.,
74      * "-Dfreetts.voices=com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory"
75      * ). If this property exists, the VoiceManager will use only this property
76      * to find voices -- no other method described below will be used. The
77      * primary purpose for this property is testing and for use with WebStart.
78      * 
79      * <p>
80      * Second, the file internal_voices.txt is looked for in the same directory
81      * as VoiceManager.class. If the file does not exist, the VoiceManager moves
82      * on. Next, it looks for voices.txt in the same directory as freetts.jar.
83      * If the file does not exist, the VoiceManager moves on. Next, if the
84      * property "freetts.voicesfile" is defined, then that file is read in. If
85      * the property is defined and the file does not exist, then an error is
86      * raised.
87      * 
88      * <P>
89      * Every voices file that is read in contains a list of VoiceDirectory class
90      * names.
91      * 
92      * <p>
93      * Next, the voice manager looks for freetts voice jarfiles that may exist
94      * in well-known locations. The directory that contains freetts.jar is
95      * searched for voice jarfiles, then directories specified by the
96      * "freetts.voicespath" system property. Any jarfile whose Manifest contains
97      * "FreeTTSVoiceDefinition: true" is assumed to be a FreeTTS voice, and the
98      * Manifest's "Main-Class" entry is assumed to be the name of the voice
99      * directory. The dependencies of the voice jarfiles specified by the
100      * "Class-Path" Manifest entry are also loaded.
101      * 
102      * <p>
103      * The VoiceManager instantiates each voice directory and calls getVoices()
104      * on each.
105      * 
106      * @return the array of new instances of all available voices
107      */
108     public Voice[] getVoices() {
109         UniqueVector voices = new UniqueVector();
110         Collection voiceDirectories = getVoiceDirectories();
111         Iterator iterator = voiceDirectories.iterator();
112         while (iterator.hasNext()) {
113             VoiceDirectory dir = (VoiceDirectory) iterator.next();
114             voices.addArray(dir.getVoices());
115         }
116
117         Voice[] voiceArray = new Voice[voices.size()];
118         return (Voice[]) voices.toArray(voiceArray);
119     }
120
121     /**
122      * Prints detailed information about all available voices.
123      * 
124      * @return a String containing the information
125      */
126     public String getVoiceInfo() {
127         String infoString = "";
128         Collection voiceDirectories = getVoiceDirectories();
129         Iterator iterator = voiceDirectories.iterator();
130         while (iterator.hasNext()) {
131             VoiceDirectory dir = (VoiceDirectory) iterator.next();
132             infoString += dir.toString();
133         }
134         return infoString;
135     }
136
137     /**
138      * Creates an array of all voice directories of all available voices using
139      * the criteria specified by the contract for getVoices().
140      * 
141      * @return the voice directories
142      * @see getVoices()
143      */
144     private Collection getVoiceDirectories() {
145         try {
146             // If there is a freetts.voices property, it means two
147             // things: 1) it is a comma separated list of class names
148             // 2) no other attempts to find voices should be
149             // made
150             //
151             // The main purpose for this property is to allow for
152             // voices to be found via WebStart.
153             //
154             String voiceClasses = System.getProperty("freetts.voices");
155             if (voiceClasses != null) {
156                 return getVoiceDirectoryNamesFromProperty(voiceClasses);
157             }
158
159             // Get voice directory names from voices files
160             UniqueVector voiceDirectoryNames = getVoiceDirectoryNamesFromFiles();
161
162             // Get list of voice jars
163             UniqueVector pathURLs = getVoiceJarURLs();
164             voiceDirectoryNames
165             .addVector(getVoiceDirectoryNamesFromJarURLs(pathURLs));
166
167             // Get dependencies
168             // Copy of vector made because vector may be modified by
169             // each call to getDependencyURLs
170             URL[] voiceJarURLs = (URL[]) pathURLs.toArray(
171                     new URL[pathURLs.size()]);
172             for (int i = 0; i < voiceJarURLs.length; i++) {
173                 getDependencyURLs(voiceJarURLs[i], pathURLs);
174             }
175
176             // If the voice jars have already been added to the classpath
177             // we avoid to add them a second time.
178             boolean noexpansion = Boolean.getBoolean("freetts.nocpexpansion");
179             if (!noexpansion) {
180                 // Extend class path
181                 for (int i = 0; i < pathURLs.size(); i++) {
182                     classLoader.addUniqueURL((URL) pathURLs.get(i));
183                 }
184             }
185
186             // Create an instance of each voice directory
187             UniqueVector voiceDirectories = new UniqueVector();
188             for (int i = 0; i < voiceDirectoryNames.size(); i++) {
189                 Class c = Class.forName((String) voiceDirectoryNames.get(i),
190                         true, classLoader);
191                 voiceDirectories.add(c.newInstance());
192             }
193
194             return voiceDirectories.elements();
195         } catch (InstantiationException e) {
196             throw new Error("Unable to load voice directory. " + e);
197         } catch (ClassNotFoundException e) {
198             throw new Error("Unable to load voice directory. " + e);
199         } catch (IllegalAccessException e) {
200             throw new Error("Unable to load voice directory. " + e);
201         }
202
203     }
204
205     /**
206      * Gets VoiceDirectory instances by parsing a comma separated String of
207      * VoiceDirectory class names.
208      */
209     private Collection getVoiceDirectoryNamesFromProperty(
210             String voiceClasses) throws InstantiationException,
211             IllegalAccessException, ClassNotFoundException {
212
213         String[] classnames = voiceClasses.split(",");
214
215         Collection directories = new java.util.ArrayList();
216
217         for (int i = 0; i < classnames.length; i++) {
218             Class c = classLoader.loadClass(classnames[i]);
219             directories.add(c.newInstance());
220         }
221
222         return directories;
223     }
224
225     /**
226      * Recursively gets the urls of the class paths that url is dependant on.
227      * 
228      * Conventions specified in
229      * http://java.sun.com/j2se/1.4.1/docs/guide/extensions/spec.html#bundled
230      * are followed.
231      * 
232      * @param url
233      *            the url to recursively check. If it ends with a "/" then it is
234      *            presumed to be a directory, and is not checked. Otherwise it
235      *            is assumed to be a jar, and its manifest is read to get the
236      *            urls Class-Path entry. These urls are passed to this method
237      *            recursively.
238      * 
239      * @param dependencyURLs
240      *            a vector containing all of the dependant urls found. This
241      *            parameter is modified as urls are added to it.
242      */
243     private void getDependencyURLs(URL url, UniqueVector dependencyURLs) {
244         try {
245             String urlDirName = getURLDirName(url);
246             if (url.getProtocol().equals("jar")) { // only check deps of jars
247
248                 // read in Class-Path attribute of jar Manifest
249                 JarURLConnection jarConnection = (JarURLConnection) url
250                         .openConnection();
251                 Attributes attributes = jarConnection.getMainAttributes();
252                 String fullClassPath = attributes
253                         .getValue(Attributes.Name.CLASS_PATH);
254                 if (fullClassPath == null || fullClassPath.equals("")) {
255                     return; // no classpaths to add
256                 }
257
258                 // The URLs are separated by one or more spaces
259                 String[] classPath = fullClassPath.split("\\s+");
260                 URL classPathURL;
261                 for (int i = 0; i < classPath.length; i++) {
262                     try {
263                         if (classPath[i].endsWith("/")) { // assume directory
264                             classPathURL = new URL("file:" + urlDirName
265                                     + classPath[i]);
266                         } else { // assume jar
267                             classPathURL = new URL("jar", "", "file:"
268                                     + urlDirName + classPath[i] + "!/");
269                         }
270                     } catch (MalformedURLException e) {
271                         System.err
272                                 .println("Warning: unable to resolve dependency "
273                                         + classPath[i]
274                                         + " referenced by "
275                                         + url);
276                         continue;
277                     }
278
279                     // don't get in a recursive loop if two jars
280                     // are mutually dependant
281                     if (!dependencyURLs.contains(classPathURL)) {
282                         dependencyURLs.add(classPathURL);
283                         getDependencyURLs(classPathURL, dependencyURLs);
284                     }
285                 }
286             }
287         } catch (IOException e) {
288             e.printStackTrace();
289         }
290     }
291
292     /**
293      * Gets the names of the subclasses of VoiceDirectory that are listed in the
294      * voices.txt files.
295      * 
296      * @return a vector containing the String names of the voice directories
297      */
298     private UniqueVector getVoiceDirectoryNamesFromFiles() {
299         try {
300             UniqueVector voiceDirectoryNames = new UniqueVector();
301
302             // first, load internal_voices.txt
303             InputStream is = this.getClass().getResourceAsStream(
304                     "internal_voices.txt");
305             if (is != null) { // if it doesn't exist, move on
306                 voiceDirectoryNames
307                         .addVector(getVoiceDirectoryNamesFromInputStream(is));
308             }
309
310             // next, try loading voices.txt
311             try {
312                 voiceDirectoryNames
313                         .addVector(getVoiceDirectoryNamesFromFile(getBaseDirectory()
314                                 + "voices.txt"));
315             } catch (FileNotFoundException e) {
316                 // do nothing
317             } catch (IOException e) {
318                 // do nothing
319             }
320
321             // last, read voices from property freetts.voicesfile
322             String voicesFile = System.getProperty("freetts.voicesfile");
323             if (voicesFile != null) {
324                 voiceDirectoryNames
325                         .addVector(getVoiceDirectoryNamesFromFile(voicesFile));
326             }
327
328             return voiceDirectoryNames;
329         } catch (IOException e) {
330             throw new Error("Error reading voices files. " + e);
331         }
332     }
333
334     /**
335      * Gets the voice directory class names from a list of urls specifying voice
336      * jarfiles. The class name is specified as the Main-Class in the manifest
337      * of the jarfiles.
338      * 
339      * @param urls
340      *            a UniqueVector of URLs that refer to the voice jarfiles
341      * 
342      * @return a UniqueVector of Strings representing the voice directory class
343      *         names
344      */
345     private UniqueVector getVoiceDirectoryNamesFromJarURLs(UniqueVector urls) {
346         try {
347             UniqueVector voiceDirectoryNames = new UniqueVector();
348             for (int i = 0; i < urls.size(); i++) {
349                 JarURLConnection jarConnection = (JarURLConnection) ((URL) urls
350                         .get(i)).openConnection();
351                 Attributes attributes = jarConnection.getMainAttributes();
352                 String mainClass = attributes
353                         .getValue(Attributes.Name.MAIN_CLASS);
354                 if (mainClass == null || mainClass.trim().equals("")) {
355                     throw new Error("No Main-Class found in jar "
356                             + (URL) urls.get(i));
357                 }
358
359                 voiceDirectoryNames.add(mainClass);
360             }
361             return voiceDirectoryNames;
362         } catch (IOException e) {
363             throw new Error("Error reading jarfile manifests. ");
364         }
365     }
366
367     /**
368      * Gets the list of voice jarfiles. Voice jarfiles are searched for in the
369      * same directory as freetts.jar and the directories specified by the
370      * freetts.voicespath system property. Voice jarfiles are defined by the
371      * manifest entry "FreeTTSVoiceDefinition: true"
372      * 
373      * @return a vector of URLs refering to the voice jarfiles.
374      */
375     private UniqueVector getVoiceJarURLs() {
376         UniqueVector voiceJarURLs = new UniqueVector();
377
378         // check in same directory as freetts.jar
379         try {
380             String baseDirectory = getBaseDirectory();
381             if (!baseDirectory.equals("")) { // not called from a jar
382                 voiceJarURLs.addVector(getVoiceJarURLsFromDir(baseDirectory));
383             }
384         } catch (FileNotFoundException e) {
385             // do nothing
386         }
387
388         // search voicespath
389         String voicesPath = System.getProperty("freetts.voicespath", "");
390         if (!voicesPath.equals("")) {
391             String[] dirNames = voicesPath.split(PATH_SEPARATOR);
392             for (int i = 0; i < dirNames.length; i++) {
393                 try {
394                     voiceJarURLs.addVector(getVoiceJarURLsFromDir(dirNames[i]));
395                 } catch (FileNotFoundException e) {
396                     throw new Error("Error loading jars from voicespath "
397                             + dirNames[i] + ". ");
398                 }
399             }
400         }
401
402         return voiceJarURLs;
403     }
404
405     /**
406      * Gets the list of voice jarfiles in a specific directory.
407      * 
408      * @return a vector of URLs refering to the voice jarfiles
409      * @see getVoiceJarURLs()
410      */
411     private UniqueVector getVoiceJarURLsFromDir(String dirName)
412             throws FileNotFoundException {
413         try {
414             UniqueVector voiceJarURLs = new UniqueVector();
415             File dir = new File(new URI("file://" + dirName));
416             if (!dir.isDirectory()) {
417                 throw new FileNotFoundException("File is not a directory: "
418                         + dirName);
419             }
420             File[] files = dir.listFiles();
421             for (int i = 0; i < files.length; i++) {
422                 File file = files[i];
423                 if (file.isFile() && (!file.isHidden())
424                         && file.getName().endsWith(".jar")) {
425                     URL jarURL = file.toURI().toURL();
426                     jarURL = new URL("jar", "", "file:" + jarURL.getPath()
427                             + "!/");
428                     JarURLConnection jarConnection = (JarURLConnection) jarURL
429                             .openConnection();
430                     // if it is not a real jar file, we will end up
431                     // with a null set of attributes.
432
433                     Attributes attributes = jarConnection.getMainAttributes();
434                     if (attributes != null) {
435                         String isVoice = attributes
436                                 .getValue("FreeTTSVoiceDefinition");
437                         if (isVoice != null && isVoice.trim().equals("true")) {
438                             voiceJarURLs.add(jarURL);
439                         }
440                     }
441                 }
442             }
443             return voiceJarURLs;
444         } catch (java.net.URISyntaxException e) {
445             throw new Error("Error reading directory name '" + dirName + "'.");
446         } catch (MalformedURLException e) {
447             throw new Error("Error reading jars from directory " + dirName
448                     + ". ");
449         } catch (IOException e) {
450             throw new Error("Error reading jars from directory " + dirName
451                     + ". ");
452         }
453     }
454
455     /**
456      * Provides a string representation of all voices available to FreeTTS.
457      * 
458      * @return a String which is a space-delimited list of voice names. If there
459      *         is more than one voice, then the word "or" appears before the
460      *         last one.
461      */
462     public String toString() {
463         String names = "";
464         Voice[] voices = getVoices();
465         for (int i = 0; i < voices.length; i++) {
466             if (i == voices.length - 1) {
467                 if (i == 0) {
468                     names = voices[i].getName();
469                 } else {
470                     names += "or " + voices[i].getName();
471                 }
472             } else {
473                 names += voices[i].getName() + " ";
474             }
475         }
476         return names;
477     }
478
479     /**
480      * Check if there is a voice provides with the given name.
481      * 
482      * @param voiceName
483      *            the name of the voice to check
484      * 
485      * @return <b>true</b> if FreeTTS has a voice available with the name
486      *         <b>voiceName</b>, else <b>false</b>.
487      */
488     public boolean contains(String voiceName) {
489         return (getVoice(voiceName) != null);
490     }
491
492     /**
493      * Get a Voice with a given name.
494      * 
495      * @param voiceName
496      *            the name of the voice to get.
497      * 
498      * @return the Voice that has the same name as <b>voiceName</b> if one
499      *         exists, else <b>null</b>
500      */
501     public Voice getVoice(String voiceName) {
502         Voice[] voices = getVoices();
503         for (int i = 0; i < voices.length; i++) {
504             if (voices[i].getName().equals(voiceName)) {
505                 return voices[i];
506             }
507         }
508         return null;
509     }
510
511     /**
512      * Get the directory that the jar file containing this class resides in.
513      * 
514      * @return the name of the directory with a trailing "/" (or equivalent for
515      *         the particular operating system), or "" if unable to determin.
516      *         (For example this class does not reside inside a jar file).
517      */
518     private String getBaseDirectory() {
519         String name = this.getClass().getName();
520         int lastdot = name.lastIndexOf('.');
521         if (lastdot != -1) { // remove package information
522             name = name.substring(lastdot + 1);
523         }
524
525         URL url = this.getClass().getResource(name + ".class");
526         return getURLDirName(url);
527     }
528
529     /**
530      * Gets the directory name from a URL
531      * 
532      * @param url
533      *            the url to parse
534      * @return the String representation of the directory name in a URL
535      */
536     private String getURLDirName(URL url) {
537         String urlFileName = url.getPath();
538         int i = urlFileName.lastIndexOf('!');
539         if (i == -1) {
540             i = urlFileName.length();
541         }
542         int dir = urlFileName.lastIndexOf("/", i);
543         if (!urlFileName.startsWith("file:")) {
544             return "";
545         }
546         return urlFileName.substring(5, dir) + "/";
547     }
548
549     /**
550      * Get the names of the voice directories from a voices file. Blank lines
551      * and lines beginning with "#" are ignored. Beginning and trailing
552      * whitespace is ignored.
553      * 
554      * @param fileName
555      *            the name of the voices file to read from
556      * 
557      * @return a vector of the names of the VoiceDirectory subclasses
558      * @throws FileNotFoundException
559      * @throws IOException
560      */
561     private UniqueVector getVoiceDirectoryNamesFromFile(String fileName)
562             throws FileNotFoundException, IOException {
563         InputStream is = new FileInputStream(fileName);
564         if (is == null) {
565             throw new IOException();
566         } else {
567             return getVoiceDirectoryNamesFromInputStream(is);
568         }
569     }
570
571     /**
572      * Get the names of the voice directories from an input stream. Blank lines
573      * and lines beginning with "#" are ignored. Beginning and trailing
574      * whitespace is ignored.
575      * 
576      * @param is
577      *            the input stream to read from
578      * 
579      * @return a vector of the names of the VoiceDirectory subclasses
580      * @throws IOException
581      */
582     private UniqueVector getVoiceDirectoryNamesFromInputStream(InputStream is)
583             throws IOException {
584         UniqueVector names = new UniqueVector();
585         BufferedReader reader = new BufferedReader(new InputStreamReader(is));
586         while (true) {
587             String line = reader.readLine();
588             if (line == null) {
589                 break;
590             }
591             line = line.trim();
592             if (!line.startsWith("#") && !line.equals("")) {
593                 names.add(line);
594             }
595         }
596         return names;
597     }
598
599     /**
600      * Gets the class loader used for loading dynamically detected jars. This is
601      * useful to get resources out of jars that may be in the class path of this
602      * class loader but not in the class path of the system class loader.
603      * 
604      * @return the class loader
605      */
606     public static URLClassLoader getVoiceClassLoader() {
607         return classLoader;
608     }
609 }
610
611 /**
612  * The DynamicClassLoader provides a means to add urls to the classpath after
613  * the class loader has already been instantiated.
614  */
615 class DynamicClassLoader extends URLClassLoader {
616
617     private java.util.HashSet classPath;
618
619     /**
620      * Constructs a new URLClassLoader for the given URLs. The URLs will be
621      * searched in the order specified for classes and resources after first
622      * searching in the specified parent class loader. Any URL that ends with a
623      * '/' is assumed to refer to a directory. Otherwise, the URL is assumed to
624      * refer to a JAR file which will be downloaded and opened as needed.
625      * 
626      * If there is a security manager, this method first calls the security
627      * manager's checkCreateClassLoader method to ensure creation of a class
628      * loader is allowed.
629      * 
630      * @param urls
631      *            the URLs from which to load classes and resources
632      * @param parent
633      *            the parent class loader for delegation
634      * 
635      * @throws SecurityException
636      *             if a security manager exists and its checkCreateClassLoader
637      *             method doesn't allow creation of a class loader.
638      */
639     public DynamicClassLoader(URL[] urls, ClassLoader parent) {
640         super(urls, parent);
641         classPath = new java.util.HashSet(urls.length);
642         for (int i = 0; i < urls.length; i++) {
643             classPath.add(urls[i]);
644         }
645     }
646
647     /**
648      * Add a URL to a class path only if has not already been added.
649      * 
650      * @param url
651      *            the url to add to the class path
652      */
653     public synchronized void addUniqueURL(final URL url) {
654         // Avoid loading of the freetts.jar.
655         final String name= url.toString();
656         if (!classPath.contains(url) && (name.indexOf("freetts.jar") < 0)) {
657             super.addURL(url);
658             classPath.add(url);
659         }
660     }
661
662     /**
663      * {@inheritDoc}
664      */
665     public Class loadClass(final String name)
666         throws ClassNotFoundException {
667         Class loadedClass = findLoadedClass(name);
668         if (loadedClass == null) {
669             try {
670                 loadedClass = findClass(name);
671             } catch (ClassNotFoundException e) {
672                 // Swallow exception
673                 // does not exist locally
674             }
675             if (loadedClass == null) {
676                 loadedClass = super.loadClass(name);
677             }
678         }
679         return loadedClass;
680     }
681 }
682
683 /**
684  * Provides a vector whose elements are always unique. The advantage over a Set
685  * is that the elements are still ordered in the way they were added. If an
686  * element is added that already exists, then nothing happens.
687  */
688 class UniqueVector {
689     private java.util.HashSet elementSet;
690     private java.util.Vector elementVector;
691
692     /**
693      * Creates a new vector
694      */
695     public UniqueVector() {
696         elementSet = new java.util.HashSet();
697         elementVector = new java.util.Vector();
698     }
699
700     /**
701      * Add an object o to the vector if it is not already present as defined by
702      * the function HashSet.contains(o)
703      * 
704      * @param o
705      *            the object to add
706      */
707     public void add(Object o) {
708         if (!contains(o)) {
709             elementSet.add(o);
710             elementVector.add(o);
711         }
712     }
713
714     /**
715      * Appends all elements of a vector to this vector. Only unique elements are
716      * added.
717      * 
718      * @param v
719      *            the vector to add
720      */
721     public void addVector(UniqueVector v) {
722         for (int i = 0; i < v.size(); i++) {
723             add(v.get(i));
724         }
725     }
726
727     /**
728      * Appends all elements of an array to this vector. Only unique elements are
729      * added.
730      * 
731      * @param a
732      *            the array to add
733      */
734     public void addArray(Object[] a) {
735         for (int i = 0; i < a.length; i++) {
736             add(a[i]);
737         }
738     }
739
740     /**
741      * Gets the number of elements currently in vector.
742      * 
743      * @return the number of elements in vector
744      */
745     public int size() {
746         return elementVector.size();
747     }
748
749     /**
750      * Checks if an element is present in the vector. The check follows the
751      * convention of HashSet contains() function, so performance can be expected
752      * to be a constant factor.
753      * 
754      * @param o
755      *            the object to check
756      * 
757      * @return true if element o exists in the vector, else false.
758      */
759     public boolean contains(Object o) {
760         return elementSet.contains(o);
761     }
762
763     /**
764      * Gets an element from a vector.
765      * 
766      * @param index
767      *            the index into the vector from which to retrieve the element
768      * 
769      * @return the object at index <b>index</b>
770      */
771     public Object get(int index) {
772         return elementVector.get(index);
773     }
774
775     /**
776      * Creates an array of the elements in the vector. Follows conventions of
777      * Vector.toArray().
778      * 
779      * @return an array representation of the object
780      */
781     public Object[] toArray() {
782         return elementVector.toArray();
783     }
784
785     /**
786      * Creates an array of the elements in the vector. Follows conventions of
787      * Vector.toArray(Object[]).
788      * 
789      * @return an array representation of the object
790      */
791     public Object[] toArray(Object[] a) {
792         return elementVector.toArray(a);
793     }
794
795     /**
796      * Returns the entries of this vector.
797      * @return elements.
798      */
799     public Collection elements() {
800         return elementVector;
801     }
802 }