001/**
002 * Portions Copyright 2001-2003 Sun Microsystems, Inc.
003 * Portions Copyright 1999-2001 Language Technologies Institute, 
004 * Carnegie Mellon University.
005 * All Rights Reserved.  Use is subject to license terms.
006 * 
007 * See the file "license.terms" for information on usage and
008 * redistribution of this file, and for a DISCLAIMER OF ALL 
009 * WARRANTIES.
010 */
011package com.sun.speech.freetts;
012
013import java.io.PrintWriter;
014import java.util.StringTokenizer;
015
016/**
017 * Represents a node in a Relation. Items can have shared contents but 
018 * each item has its own set of Daughters. The shared contents of an
019 * item (represented by ItemContents) includes the feature set for the
020 * item and the set of all relations that this item is contained in.
021 * An item can be contained in a number of relations and as daughters
022 * to other items. This class is used to keep track of all of these
023 * relationships. There may be many instances of item that reference
024 * the same shared ItemContents. 
025 */
026public class Item implements Dumpable {
027    private Relation ownerRelation;
028    private ItemContents contents;
029    private Item parent;
030    private Item daughter;
031    private Item next;
032    private Item prev;
033
034
035    /**
036     * Creates an item. The item is coupled to a particular
037     * Relation. If shared contents is null a new sharedContents is
038     * created.
039     *
040     * @param relation the relation that owns this item
041     * @param sharedContents the contents that is shared with others.
042     *   If null, a new sharedContents is created.
043     */
044    public Item(Relation relation, ItemContents sharedContents) {
045        ownerRelation = relation;
046        if (sharedContents != null) {
047            contents = sharedContents;
048        } else {
049            contents = new ItemContents();
050        }
051        parent = null;
052        daughter = null;
053        next = null;
054        prev = null;
055
056
057        getSharedContents().addItemRelation(relation.getName(), this);
058    }
059
060    /**
061     * Finds the item in the given relation that has the same shared
062     * contents.
063     *
064     * @param relationName the relation of interest
065     *
066     * @return the item as found in the given relation or null if not
067     *   found
068     */
069    public Item getItemAs(String relationName) {
070        return getSharedContents().getItemRelation(relationName);
071    }
072
073
074    /**
075     * Retrieves the owning Relation.
076     *
077     * @return the relation that owns this item
078     */
079    public Relation getOwnerRelation() {
080        return ownerRelation;
081    }
082
083    /**
084     * Retrieves the shared contents for this item.
085     *
086     * @return the shared item contents
087     */
088    public ItemContents getSharedContents() {
089        return contents;
090    }
091
092    /**
093     * Determines if this item has daughters.
094     *
095     * @return true if this item has daughters
096     */
097    public boolean hasDaughters() {
098        return daughter != null;
099    }
100
101    /**
102     * Retrieves the first daughter of this item.
103     *
104     * @return the first daughter or null if none
105     */
106    public Item getDaughter() {
107        return daughter;
108    }
109    
110    /**
111     * Retrieves the Nth daughter of this item.
112     *
113     * @param which the index of the daughter to return
114     *
115     * @return the Nth daughter or null if none at the given index
116     */
117    public Item getNthDaughter(int which) {
118        Item d = daughter;
119        int count = 0;
120        while (count++ != which && d != null) {
121            d = d.next;
122        }
123        return d;
124    }
125
126    /**
127     * Retrieves the last daughter of this item.
128     *
129     * @return the last daughter or null if none at the given index
130     */
131    public Item getLastDaughter() {
132        Item d = daughter;
133        if (d == null) {
134            return null;
135        }
136        while (d.next != null) {
137            d = d.next;
138        }
139        return d;
140    }
141
142    /**
143     * Adds the given item as a daughter to this item.
144     *
145     * @param item the new daughter
146     */
147    public Item addDaughter(Item item) {
148        Item newItem;
149        ItemContents  contents;
150
151        Item p = getLastDaughter();
152
153        if (p != null) {
154            newItem = p.appendItem(item);
155        } else {
156            if (item == null) {
157                contents = new ItemContents();
158            } else {
159                contents = item.getSharedContents();
160            }
161            newItem = new Item(getOwnerRelation(), contents);
162            newItem.parent = this;
163            daughter = newItem;
164        }
165        return newItem;
166    }
167
168    /**
169     * Creates a new Item, adds it as a daughter to this item
170     * and returns the new item.
171     *
172     * @return the newly created item that was added as a daughter
173     */
174    public Item createDaughter() {
175        return addDaughter(null);
176    }
177
178
179    /**
180     * Returns the parent of this item.
181     *
182     * @return the parent of this item
183     */
184    public Item getParent() {
185        Item n;
186        for (n = this; n.prev != null; n = n.prev) {
187            if (n == null) {
188                return null;
189            }
190        }
191        return n.parent;
192    }
193
194
195    /**
196     * Sets the parent of this item.
197     *
198     * @param parent the parent of this item
199     */
200    /*
201    private void setParent(Item parent) {
202        this.parent = parent;
203    }
204    */
205
206    /**
207     * Returns the utterance associated with this item.
208     *
209     * @return the utterance that contains this item
210     */
211    public Utterance getUtterance() {
212        return getOwnerRelation().getUtterance();
213    }
214
215    /**
216     * Returns the feature set of this item.
217     *
218     * @return the feature set of this item
219     */
220    public FeatureSet getFeatures() {
221        return getSharedContents().getFeatures();
222    }
223
224    /**
225     * Dumps out this item to the given output stream.
226     *
227     * @param out where to send the output
228     * @param pad the leading whitspace
229     */
230    public void dump(PrintWriter out, int pad, String title) {
231        String itemName = title + ":" + toString();
232        getFeatures().dump(out, pad, itemName);
233        if (hasDaughters()) {
234            Item daughter = getDaughter();
235            while (daughter != null) {
236                daughter.dump(out, pad + 8, "d");
237                daughter = daughter.next;
238            }
239        }
240    }
241
242    /**
243     * Finds the feature by following the given path.
244     * Path is a string of
245     *   ":" or "." separated strings with the following interpretations:
246     * <ul>
247     * <li> n - next item
248     * <li> p - previous item
249     * <li> parent - the parent
250     * <li> daughter - the daughter
251     * <li> daughter1 - same as daughter
252     * <li> daughtern - the last daughter
253     * <li> R:relname - the item as found in the given relation 'relname'
254     * </ul>
255     * The last element of the path will be interpreted as a
256     * voice/language specific feature function (if present) or an
257     * item feature name. If the feature function exists it will be
258     * called with the item specified by the path, otherwise, a
259     * feature will be retrieved with the given name. If neither exist
260     * than a String "0" is returned.
261     *
262     * @param pathAndFeature the path to follow
263     */
264    public Object findFeature(String pathAndFeature) {
265        int lastDot;
266        String feature;
267        String path;
268        Item item;
269        FeatureProcessor fp;
270
271        Voice voice = getOwnerRelation().getUtterance().getVoice();
272        Object results = null;
273        
274
275        lastDot = pathAndFeature.lastIndexOf(".");
276        // string can be of the form "p.feature" or just "feature"
277
278        if (lastDot == -1) {
279            feature = pathAndFeature;
280            path = null;
281        } else {
282            feature = pathAndFeature.substring(lastDot + 1);
283            path = pathAndFeature.substring(0, lastDot);
284        }
285
286
287        item = findItem(path);
288        if (item != null) {
289            fp = voice.getFeatureProcessor(feature);
290
291            if (fp != null) {
292                try {
293                    results = fp.process(item);
294                } catch (ProcessException pe) {
295                    System.err.println("Trouble while processing " +
296                                       fp.toString());
297                }
298            } else {
299                results = item.getFeatures().getObject(feature);
300            }
301        }
302        results =  (results == null)
303                ? "0" 
304                : results;
305
306    // System.out.println("FI " + pathAndFeature + " are " + results);
307
308        return results;
309    }
310
311    /** 
312     * Finds the item specified by the given path.
313     
314     * Path is a string of ":" or
315     * "." separated strings with the following interpretations:
316     * <ul>
317     * <li> n - next item
318     * <li> p - previous item
319     * <li> parent - the parent
320     * <li> daughter - the daughter
321     * <li> daughter1 - same as daughter
322     * <li> daughtern - the last daughter
323     * <li> R:relname - the item as found in the given relation 'relname'
324     * </ul>
325     * If the given path takes us outside of the bounds of the item
326     * graph, then list access exceptions will be thrown.
327     *
328     * @param path the path to follow
329     *
330     * @return the item at the given path
331     */
332    public Item findItem(String path) {
333        Item pitem = this;
334        StringTokenizer tok;
335        
336        if (path == null) {
337            return this;
338        }
339
340        tok = new StringTokenizer(path, ":.");
341
342        while (pitem != null && tok.hasMoreTokens()) {
343            String token = tok.nextToken();
344            if (token.equals("n")) {
345                pitem = pitem.getNext();
346            } else if (token.equals("p")) {
347                pitem = pitem.getPrevious();
348            } else if (token.equals("nn")) {
349                pitem = pitem.getNext();
350                if (pitem != null) {
351                    pitem = pitem.getNext();
352                }
353            } else if (token.equals("pp")) {
354                pitem = pitem.getPrevious();
355                if (pitem != null) {
356                    pitem = pitem.getPrevious();
357                }
358            } else if (token.equals("parent")) {
359                pitem = pitem.getParent();
360            } else if (token.equals("daughter") || token.equals("daughter1")) {
361                pitem = pitem.getDaughter();
362            } else if (token.equals("daughtern")) {
363                pitem = pitem.getLastDaughter();
364            } else if (token.equals("R")) {
365                String relationName = tok.nextToken();
366                pitem = pitem.getSharedContents().getItemRelation(relationName);
367            } else {
368                System.out.println("findItem: bad feature " + token +
369                        " in " + path);
370            }
371        }
372        return pitem;
373    }
374
375
376    /**
377     * Gets the next item in this list.
378     *
379     * @return the next item or null
380     */
381    public Item getNext() {
382        return next;
383    }
384
385
386    /**
387     * Gets the previous item in this list.
388     *
389     * @return the previous item or null
390     */
391    public Item getPrevious() {
392        return prev;
393    }
394
395
396    /**
397     * Appends an item in this list after this item.
398     *
399     * @param originalItem new item has shared contents with this 
400     *   item (or * null)
401     *
402     * @return the newly appended item
403     */
404    public Item appendItem(Item originalItem) {
405        ItemContents contents;
406        Item newItem;
407
408        if (originalItem == null) {
409            contents = null;
410        } else {
411            contents = originalItem.getSharedContents();
412        }
413
414        newItem = new Item(getOwnerRelation(), contents);
415        newItem.next = this.next;
416        if (this.next != null) {
417            this.next.prev = newItem;
418        }
419
420        attach(newItem);
421
422        if (this.ownerRelation.getTail() == this) {
423                this.ownerRelation.setTail(newItem);
424        }
425        return newItem;
426    }
427
428    /**
429     * Attaches/appends an item to this one.
430     *
431     * @param item the item to append
432     */
433    void attach(Item item) {
434        this.next = item;
435        item.prev = this;
436    }
437
438    /**
439     * Prepends an item in this list before this item.
440     *
441     * @param originalItem new item has shared contents with this 
442     *   item (or * null)
443     *
444     * @return the newly appended item
445     */
446    public Item prependItem(Item originalItem) {
447        ItemContents contents;
448        Item newItem;
449
450        if (originalItem == null) {
451            contents = null;
452        } else {
453            contents = originalItem.getSharedContents();
454        }
455
456        newItem = new Item(getOwnerRelation(), contents);
457        newItem.prev = this.prev;
458        if (this.prev != null) {
459            this.prev.next = newItem;
460        }
461        newItem.next = this;
462        this.prev = newItem;
463        if (this.parent != null) {
464            this.parent.daughter = newItem;
465            newItem.parent = this.parent;
466            this.parent = null;
467        }
468        if (this.ownerRelation.getHead() == this) {
469            this.ownerRelation.setHead(newItem);
470        }
471        return newItem;
472    }
473
474
475
476    // Inherited from object
477    public String toString() {
478        // if we have a feature called 'name' use that
479        // otherwise fall back on the default.
480        String name = getFeatures().getString("name");
481        if (name == null) {
482            name = "";
483        }
484        return name;
485    }
486
487    /**
488     * Determines if the shared contents of the two items are the same.
489     *
490     * @param otherItem the item to compare
491     *
492     * @return true if the shared contents are the same
493     */
494    public boolean equalsShared(Item otherItem) {
495        if (otherItem == null) {
496            return false;
497        } else {
498            return getSharedContents().equals(otherItem.getSharedContents());
499        }
500    }
501}