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