001/* An interface used by the expression parser for identifier lookup.
002
003 Copyright (c) 2001-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027
028 */
029package ptolemy.data.expr;
030
031import java.util.HashSet;
032import java.util.Iterator;
033import java.util.List;
034import java.util.Set;
035
036import ptolemy.data.Token;
037import ptolemy.kernel.ComponentEntity;
038import ptolemy.kernel.ComponentRelation;
039import ptolemy.kernel.CompositeEntity;
040import ptolemy.kernel.Entity;
041import ptolemy.kernel.Port;
042import ptolemy.kernel.Relation;
043import ptolemy.kernel.util.Attribute;
044import ptolemy.kernel.util.IllegalActionException;
045import ptolemy.kernel.util.LazyComposite;
046import ptolemy.kernel.util.NamedObj;
047import ptolemy.kernel.util.ScopeExtender;
048
049//////////////////////////////////////////////////////////////////////////
050//// ModelScope
051
052/**
053 An abstract class that is useful for implementing expression language
054 scopes for Ptolemy models.
055
056 <p>{@link #getScopedVariable(Variable, NamedObj, String)} is the
057 primary entry point, used by the Expression actor and other code
058 to look up Variables by name.</p>
059
060 @author Xiaojun Liu, Steve Neuendorffer, Contributor: Bert Rodiers
061 @version $Id$
062 @since Ptolemy II 2.1
063 @Pt.ProposedRating Red (liuxj)
064 @Pt.AcceptedRating Red (liuxj)
065 @see ptolemy.data.expr.PtParser
066 */
067public abstract class ModelScope implements ParserScope {
068    /** Return a list of object names in scope for variables in the
069     * given container.
070     * @param container The container of this scope.
071     * @return a list of object names in scope for variables in the
072     * given container.
073     */
074    public static Set<String> getAllScopedObjectNames(NamedObj container) {
075        Set<String> identifiers = new HashSet<String>();
076        identifiers.add("this");
077        while (container != null) {
078            for (Object attribute : container.attributeList()) {
079                identifiers.add(((Attribute) attribute).getName());
080            }
081            if (container instanceof Entity) {
082                for (Object port : ((Entity) container).portList()) {
083                    identifiers.add(((Port) port).getName());
084                }
085            }
086            if (container instanceof CompositeEntity) {
087                for (Object entity : ((CompositeEntity) container)
088                        .entityList()) {
089                    identifiers.add(((Entity) entity).getName());
090                }
091
092                for (Object relation : ((CompositeEntity) container)
093                        .relationList()) {
094                    identifiers.add(((Relation) relation).getName());
095                }
096            }
097            container = container.getContainer();
098        }
099
100        return identifiers;
101    }
102
103    /** Return a list of variable names in scope for variables in the
104     * given container.  Exclude the given variable from being
105     * considered in scope.
106     * @param exclude  The variable to exclude from the scope.
107     * @param container The container of this scope.
108     * @return A list of variable names in scope for variables in the
109     * given container.
110     */
111    public static Set<String> getAllScopedVariableNames(Variable exclude,
112            NamedObj container) {
113        List variableList = container.attributeList(Variable.class);
114        variableList.remove(exclude);
115
116        Set<String> nameSet = new HashSet<String>();
117
118        for (Iterator variables = variableList.iterator(); variables
119                .hasNext();) {
120            Variable variable = (Variable) variables.next();
121            nameSet.add(variable.getName());
122        }
123
124        // Get variables higher in scope.  Moving up the hierarchy
125        // terminates when the container is null.
126        NamedObj aboveContainer = container.getContainer();
127
128        if (aboveContainer != null) {
129            nameSet.addAll(getAllScopedVariableNames(exclude, aboveContainer));
130        }
131
132        // Get variables in scope extenders.  Moving down the scope
133        // extenders terminates at hierarchy leaves.
134        Iterator extenders = container.attributeList(ScopeExtender.class)
135                .iterator();
136
137        while (extenders.hasNext()) {
138            ScopeExtender extender = (ScopeExtender) extenders.next();
139            // It would be nice if ScopeExtender and NamedObj were common in
140            // some way to avoid this cast.
141
142            // This change was necessary for Java Code Generation.
143            // We don't want to call getAllScopedVariableNames() here because
144            // we will end up in an endless loop.
145
146            // Test 3.1 in ptolemy/actor/parameters/test/ParameterSetModel.tcl
147            // will go in an endless loop if the next two lines are on commente:
148            //nameSet.addAll(getAllScopedVariableNames(exclude,
149            //        (NamedObj) extender));
150
151            // This is safer, but does it include everything?
152            for (Iterator attributes = extender.attributeList()
153                    .iterator(); attributes.hasNext();) {
154                Attribute attribute = (Attribute) attributes.next();
155                if (attribute instanceof Variable) {
156                    nameSet.add(attribute.getName());
157                }
158            }
159        }
160        return nameSet;
161    }
162
163    /** Get the attribute with the given name in the scope of the given
164     *  container.  If the name contains the "::" scoping specifier,
165     *  then an attribute more deeply in the hierarchy is searched
166     *  for.  The scope of the object includes any container of the
167     *  given object, and any attribute contained in a scope extending
168     *  attribute inside any of those containers.
169     *  @param exclude An attribute to exclude from the search.
170     *  @param container The container to search upwards from.
171     *  @param name The attribute name to search for.
172     *  @return The attribute with the given name or null if the attribute
173     *  does not exist.
174     */
175    public static Attribute getScopedAttribute(Attribute exclude,
176            NamedObj container, String name) {
177        // getScopedAttribute() is used by the SetVariable actor.
178        String insideName = name.replaceAll("::", ".");
179
180        while (container != null) {
181            Attribute result = _searchAttributeIn(exclude, container,
182                    insideName);
183
184            if (result != null) {
185                return result;
186            } else {
187                List attributes = container
188                        .attributeList(ContainmentExtender.class);
189                Iterator attrIterator = attributes.iterator();
190                NamedObj extendedContainer = null;
191                while (extendedContainer == null && attrIterator.hasNext()) {
192                    ContainmentExtender extender = (ContainmentExtender) attrIterator
193                            .next();
194                    try {
195                        extendedContainer = extender.getExtendedContainer();
196                    } catch (IllegalActionException e) {
197                        // Ignore the exception, and try the next extender.
198                    }
199                }
200
201                if (extendedContainer == null) {
202                    container = container.getContainer();
203                } else {
204                    container = extendedContainer;
205                }
206            }
207        }
208
209        return null;
210    }
211
212    /** Get the NamedObj with the given name in the scope of the given
213     *  container.  If the name contains the "::" scoping specifier,
214     *  then an attribute more deeply in the hierarchy is searched
215     *  for. If the specified container is lazy (implements
216     *  LazyComposite), then references to its contained entities
217     *  or relations will not resolve, so such references are disallowed.
218     *  @param container The container to search upwards from.
219     *  @param name The object name to search for.
220     *  @return The NamedObj with the given name or null if the NamedObj
221     *  does not exist.
222     */
223    public static NamedObj getScopedObject(NamedObj container, String name) {
224        if (name.equals("this")) {
225            return container;
226        }
227
228        String[] parts = name.replaceAll("::", ".").split("\\.");
229        NamedObj result = null;
230        boolean lookup = true;
231        for (String part : parts) {
232            result = null;
233            while (container != null) {
234                //Attribute attribute = container.getAttribute(part);
235                Attribute attribute = _searchAttributeIn(null, container, part);
236                if (attribute != null) {
237                    result = attribute;
238                } else {
239                    if (container instanceof Entity) {
240                        Port port = ((Entity) container).getPort(part);
241                        if (port != null) {
242                            result = port;
243                        } else if (container instanceof CompositeEntity) {
244                            // NOTE: Lazy composites cannot have references to their
245                            // contained entities or relations. This would defeat the
246                            // lazy mechanism, forcing the actor to populate its
247                            // contents.
248                            if (!(container instanceof LazyComposite)) {
249                                ComponentEntity entity = ((CompositeEntity) container)
250                                        .getEntity(part);
251                                if (entity != null) {
252                                    result = entity;
253                                } else {
254                                    ComponentRelation relation = ((CompositeEntity) container)
255                                            .getRelation(part);
256                                    if (relation != null) {
257                                        result = relation;
258                                    }
259                                }
260                            }
261                        }
262                    }
263                }
264                if (lookup && result == null) {
265                    List attributes = container
266                            .attributeList(ContainmentExtender.class);
267                    Iterator attrIterator = attributes.iterator();
268                    NamedObj extendedContainer = null;
269                    while (extendedContainer == null
270                            && attrIterator.hasNext()) {
271                        ContainmentExtender extender = (ContainmentExtender) attrIterator
272                                .next();
273                        try {
274                            extendedContainer = extender.getExtendedContainer();
275                        } catch (IllegalActionException e) {
276                            // Ignore the exception, and try the next extender.
277                        }
278                    }
279
280                    if (extendedContainer == null) {
281                        container = container.getContainer();
282                    } else {
283                        container = extendedContainer;
284                    }
285                } else {
286                    break;
287                }
288            }
289            if (result == null) {
290                break;
291            }
292            container = result;
293            lookup = false;
294        }
295
296        return result;
297    }
298
299    /** Get the variable with the given name in the scope of the given
300     *  container.  If the name contains the "::" scoping specifier,
301     *  then an attribute more deeply in the hierarchy is searched
302     *  for.  The scope of the object includes any container of the
303     *  given object, and any variable contained in a scope extending
304     *  attribute inside any of those containers.
305     *  @param exclude A variable to exclude from the search.
306     *  @param container The container to search upwards from.
307     *  @param name The variable name to search for.
308     *  @return The variable with the given name or null if the variable
309     *  does not exist.
310     */
311    public static Variable getScopedVariable(Variable exclude,
312            NamedObj container, String name) {
313        // This is the primary entry point for this class, used
314        // by the Expression actor and others.
315        String insideName = name.replaceAll("::", ".");
316
317        while (container != null) {
318            Variable result = _searchVariableIn(exclude, container, insideName);
319
320            if (result != null) {
321                return result;
322            } else {
323                List attributes = container
324                        .attributeList(ContainmentExtender.class);
325                Iterator attrIterator = attributes.iterator();
326                NamedObj extendedContainer = null;
327                while (extendedContainer == null && attrIterator.hasNext()) {
328                    ContainmentExtender extender = (ContainmentExtender) attrIterator
329                            .next();
330                    try {
331                        extendedContainer = extender.getExtendedContainer();
332                    } catch (IllegalActionException e) {
333                        // Ignore the exception, and try the next extender.
334                    }
335                }
336
337                if (extendedContainer == null) {
338                    container = container.getContainer();
339                } else {
340                    container = extendedContainer;
341                }
342            }
343        }
344
345        return null;
346    }
347
348    /** Check to see whether a preference of the specified name is
349     *  defined in the specified context, and if it is, return its value.
350     *  Note that if there is an error in the expression for the preference,
351     *  then this method will return null and report the error to standard out.
352     *  This is done because we assume the error will normally be caught
353     *  before this method is called.
354     *  @param context The context for the preference.
355     *  @param preferenceName The name of the preference.
356     *  @return The value of the preference, or null if it is not set.
357     */
358    public static Token preferenceValue(NamedObj context,
359            String preferenceName) {
360        Variable result = ModelScope.getScopedVariable(null, context,
361                preferenceName);
362
363        if (result != null) {
364            try {
365                return result.getToken();
366            } catch (IllegalActionException ex) {
367                System.out.println("Warning: Invalid preference: " + ex);
368            }
369        }
370
371        // If no scoped variable is found, try for a defined constant.
372        return Constants.get(preferenceName);
373    }
374
375    // Search in the container for an attribute with the given name.
376    // Search recursively in any instance of ScopeExtender in the
377    // container.
378    private static Attribute _searchAttributeIn(Attribute exclude,
379            NamedObj container, String name) {
380        Attribute result = container.getAttribute(name);
381
382        if (result != null && result != exclude) {
383            return result;
384        } else {
385            Iterator extenders = container.attributeList(ScopeExtender.class)
386                    .iterator();
387
388            while (extenders.hasNext()) {
389                ScopeExtender extender = (ScopeExtender) extenders.next();
390                result = extender.getAttribute(name);
391
392                if (result != null && result != exclude) {
393                    return result;
394                }
395            }
396        }
397
398        return null;
399    }
400
401    // Search in the container for a variable with the given name.
402    // Search recursively in any instance of ScopeExtender in the
403    // container.
404    private static Variable _searchVariableIn(Variable exclude,
405            NamedObj container, String name) {
406        Attribute result = container.getAttribute(name);
407
408        if (result != null && result instanceof Variable && result != exclude) {
409            return (Variable) result;
410        } else {
411            Iterator extenders = container.attributeList(ScopeExtender.class)
412                    .iterator();
413
414            while (extenders.hasNext()) {
415                ScopeExtender extender = (ScopeExtender) extenders.next();
416                result = extender.getAttribute(name);
417
418                if (result != null && result instanceof Variable
419                        && result != exclude) {
420                    return (Variable) result;
421                }
422            }
423        }
424
425        return null;
426    }
427}