001/**
002 * Portions Copyright 2001 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.util.ArrayList;
014import java.util.HashMap;
015import java.util.List;
016import java.util.Map;
017import java.util.StringTokenizer;
018import java.util.logging.Level;
019import java.util.logging.Logger;
020
021import com.sun.speech.freetts.util.Utilities;
022
023
024/**
025 * Interface that Manages a feature or item path. Allows navigation
026 * to the corresponding feature or item.
027 * This class in controlled by the following system properties:
028 *
029 * <pre>
030 *   com.sun.speech.freetts.interpretCartPaths - default false
031 *   com.sun.speech.freetts.lazyCartCompile - default true
032 * </pre>
033 *   com.sun.speech.freetts.interpretCartPaths
034 *
035 * Instances of this class will optionally pre-compile the paths.
036 * Pre-compiling paths reduces the processing time and objects needed
037 * to extract a feature or an item based upon a path.
038 */
039public class PathExtractorImpl implements PathExtractor {
040    /** Logger instance. */
041    private static final Logger LOGGER =
042        Logger.getLogger(PathExtractorImpl.class.getName());
043
044    /**
045      * If this system property is set to true, paths will
046      * not be compiled.
047      */
048    public final static String INTERPRET_PATHS_PROPERTY =
049        "com.sun.speech.freetts.interpretCartPaths";
050
051    /**
052     * If this system property is set to true, CART feature/item
053     * paths will only be compiled as needed.
054     */
055    public final static String LAZY_COMPILE_PROPERTY =
056        "com.sun.speech.freetts.lazyCartCompile";
057
058    private final static boolean INTERPRET_PATHS = 
059        Utilities.getProperty(INTERPRET_PATHS_PROPERTY, "false").equals("true");
060    private final static boolean LAZY_COMPILE  = 
061        Utilities.getProperty(LAZY_COMPILE_PROPERTY, "true").equals("true");
062
063    private String pathAndFeature;
064    private String path;
065    private String feature;
066    private Object[] compiledPath;
067    private boolean wantFeature = false;
068
069    /**
070     * Creates a path for the given feature.
071     */
072    public PathExtractorImpl(String pathAndFeature, boolean wantFeature) {
073        this.pathAndFeature = pathAndFeature;
074        if (INTERPRET_PATHS)  {
075            path = pathAndFeature;
076            return;
077        }
078    
079        if (wantFeature) {
080            int lastDot = pathAndFeature.lastIndexOf(".");
081            // string can be of the form "p.feature" or just "feature"
082
083            if (lastDot == -1) {
084                feature = pathAndFeature;
085                path = null;
086            } else {
087                feature = pathAndFeature.substring(lastDot + 1);
088                path = pathAndFeature.substring(0, lastDot);
089            }
090            this.wantFeature = wantFeature;
091        } else {
092            this.path = pathAndFeature;
093        }
094
095        if (!LAZY_COMPILE) {
096            compiledPath = compile(path);
097        }
098    }
099
100    /**
101     * Finds the item associated with this Path.
102     * @param item the item to start at
103     * @return the item associated with the path or null
104     */
105    public Item findItem(Item item) {
106
107        if (INTERPRET_PATHS) {
108            return item.findItem(path);
109        }
110
111        if (compiledPath == null) {
112            compiledPath = compile(path);
113        }
114
115        Item pitem = item;
116
117        for (int i = 0; pitem != null && i < compiledPath.length; ) {
118            OpEnum op = (OpEnum) compiledPath[i++];
119            if (op == OpEnum.NEXT) {
120                pitem = pitem.getNext();
121            } else if (op == OpEnum.PREV) {
122                pitem = pitem.getPrevious();
123            } else if (op == OpEnum.NEXT_NEXT) {
124                pitem = pitem.getNext();
125                if (pitem != null) {
126                    pitem = pitem.getNext();
127                }
128            } else if (op == OpEnum.PREV_PREV) {
129                pitem = pitem.getPrevious();
130                if (pitem != null) {
131                    pitem = pitem.getPrevious();
132                }
133            } else if (op == OpEnum.PARENT) {
134                pitem = pitem.getParent();
135            } else if (op == OpEnum.DAUGHTER) {
136                pitem = pitem.getDaughter();
137            } else if (op == OpEnum.LAST_DAUGHTER) {
138                pitem = pitem.getLastDaughter();
139            } else if (op == OpEnum.RELATION) {
140                String relationName = (String) compiledPath[i++];
141                pitem = pitem.getSharedContents().getItemRelation(relationName);
142            } else {
143                System.out.println("findItem: bad feature " + op +
144                        " in " + path);
145            }
146        }
147        return pitem;
148    }
149
150
151    /**
152     * Finds the feature associated with this Path.
153     * @param item the item to start at
154     * @return the feature associated or "0"  if the
155     * feature was not found.
156     */
157    public Object findFeature(Item item) {
158
159        if (INTERPRET_PATHS) {
160            return item.findFeature(path);
161        }
162
163        Item pitem = findItem(item);
164        Object results = null;
165        if (pitem != null) {
166                if (LOGGER.isLoggable(Level.FINER)) {
167                    LOGGER.finer("findFeature: Item [" + pitem + "], feature '" 
168                            + feature + "'");
169                }
170
171            FeatureProcessor fp =
172                pitem.getOwnerRelation().getUtterance().
173                    getVoice().getFeatureProcessor(feature);
174
175            if (fp != null) {
176                if (LOGGER.isLoggable(Level.FINER)) {
177                    LOGGER.finer(
178                            "findFeature: There is a feature processor for '" 
179                            + feature + "'");
180                }
181                try {
182                    results = fp.process(pitem);
183                } catch (ProcessException pe) {
184                    LOGGER.severe("trouble while processing " + fp);
185                     throw new Error(pe);
186                }
187            } else {
188                results = pitem.getFeatures().getObject(feature);
189            }
190        }
191
192        results = (results == null) ? "0" : results;
193        if (LOGGER.isLoggable(Level.FINER)) {
194            LOGGER.finer("findFeature: ...results = '" + results + "'");
195        }
196        return results;
197    }
198
199
200    /**
201     * Compiles the given path into the compiled form
202     * @param path the path to compile
203     * @return the compiled form which is in the form
204     * of an array path traversal enums and associated strings
205     */
206    private Object[] compile(String path) {
207        List list = new ArrayList();
208
209        if (path == null) {
210            return list.toArray();
211        }
212
213        StringTokenizer tok = new StringTokenizer(path, ":.");
214
215        while (tok.hasMoreTokens()) {
216            String token = tok.nextToken();
217            OpEnum op = OpEnum.getInstance(token);
218            if (op == null) {
219                throw new Error("Bad path compiled " + path);
220            } 
221
222            list.add(op);
223
224            if (op == OpEnum.RELATION) {
225                list.add(tok.nextToken());
226            }
227        }
228        return list.toArray();
229    }
230
231    // inherited for Object
232
233    public String toString() {
234        return pathAndFeature;
235    }
236
237
238    // TODO: add these to the interface should we support binary
239    // files
240    /*
241    public void writeBinary();
242    public void readBinary();
243    */
244}
245
246
247/**
248 * An enumerated type associated with path operations.
249 */
250class OpEnum {
251    static private Map map = new HashMap();
252
253    public final static OpEnum NEXT = new OpEnum("n");
254    public final static OpEnum PREV = new OpEnum("p");
255    public final static OpEnum NEXT_NEXT = new OpEnum("nn");
256    public final static OpEnum PREV_PREV = new OpEnum("pp");
257    public final static OpEnum PARENT = new OpEnum("parent");
258    public final static OpEnum DAUGHTER = new OpEnum("daughter");
259    public final static OpEnum LAST_DAUGHTER = new OpEnum("daughtern");
260    public final static OpEnum RELATION = new OpEnum("R");
261
262    private String name;
263
264    /**
265     * Creates a new OpEnum.. There is a limited
266     * set of OpEnums
267     * @param name the path name for this Enum
268     */
269    private OpEnum(String name) {
270        this.name = name;
271        map.put(name, this);
272    }
273
274    /**
275     * gets an OpEnum thats associated with
276     * the given name.
277     * @param name the name of the OpEnum of interest
278     */
279    public static OpEnum getInstance(String name) {
280        return (OpEnum) map.get(name);
281    }
282
283    // inherited from Object
284    public String toString() {
285        return name;
286    }
287}