001/* Check that all the variables are defined in a piece of MoML.
002
003Copyright (c) 2007-2014 The Regents of the University of California.
004All rights reserved.
005Permission is hereby granted, without written agreement and without
006license or royalty fees, to use, copy, modify, and distribute this
007software and its documentation for any purpose, provided that the above
008copyright notice and the following two paragraphs appear in all copies
009of this software.
010
011IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015SUCH DAMAGE.
016
017THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022ENHANCEMENTS, OR MODIFICATIONS.
023
024PT_COPYRIGHT_VERSION_2
025COPYRIGHTENDKEY
026
027 */
028package ptolemy.moml;
029
030import java.io.StringWriter;
031import java.util.Iterator;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Set;
035
036import ptolemy.actor.TypedCompositeActor;
037import ptolemy.data.expr.ASTPtRootNode;
038import ptolemy.data.expr.ModelScope;
039import ptolemy.data.expr.ParseTreeFreeVariableCollector;
040import ptolemy.data.expr.ParserScope;
041import ptolemy.data.expr.PtParser;
042import ptolemy.data.expr.UndefinedConstantOrIdentifierException;
043import ptolemy.data.expr.Variable;
044import ptolemy.kernel.CompositeEntity;
045import ptolemy.kernel.Entity;
046import ptolemy.kernel.util.AbstractSettableAttribute;
047import ptolemy.kernel.util.Attribute;
048import ptolemy.kernel.util.IllegalActionException;
049import ptolemy.kernel.util.NamedObj;
050import ptolemy.kernel.util.Workspace;
051
052///////////////////////////////////////////////////////////////////
053//// MoMLUtilties
054
055/**
056   Check that all the variables are defined in a piece of MoML.
057
058   @author Christopher Brooks
059   @version $Id$
060   @since Ptolemy II 6.1
061   @Pt.ProposedRating Red (cxh)
062   @Pt.AcceptedRating Red (cxh)
063 */
064public class MoMLVariableChecker {
065
066    ///////////////////////////////////////////////////////////////////
067    ////                         public methods                    ////
068
069    /** Check for problems in the moml to be copied.  If there are
070     *  missing variables references, search for the variables and
071     *  return MoML definitions for any found variables.
072     *  @param momlToBeChecked The MoML string to be checked.
073     *  @param container The container in which the string is to be checked.
074     *  @return MoML to be inserted before the momlToBeChecked
075     *  @exception IllegalActionException If there is a problem parsing
076     *  the string, or validating a variable.
077     */
078    public String checkCopy(String momlToBeChecked, NamedObj container)
079            throws IllegalActionException {
080
081        return checkCopy(momlToBeChecked, container, false);
082    }
083
084    /** Check for problems in the moml to be copied.  If there are
085     *  missing variable references, search for the variables and
086     *  return MoML definitions for any found variables.
087     *  @param momlToBeChecked The MoML string to be checked.
088     *  @param container The container in which the string is to be checked.
089     *  @param hideVariables If true, add MoML that will make all the found
090     *   variables hidden from the user interface when they are copied.
091     *  @return MoML to be inserted before the momlToBeChecked
092     *  @exception IllegalActionException If there is a problem parsing
093     *  the string, or validating a variable.
094     */
095    public String checkCopy(String momlToBeChecked, NamedObj container,
096            boolean hideVariables) throws IllegalActionException {
097
098        _variableBuffer = new StringWriter();
099        Workspace workspace = new Workspace("copyWorkspace");
100        MoMLParser parser = new MoMLParser(workspace);
101        TypedCompositeActor parsedContainer = null;
102
103        // Attempt to parse the moml.  If we fail, check the exception
104        // for a missing variable.  If a missing variable is found
105        // add it to the moml and reparse.
106        boolean doParse = true;
107        while (doParse) {
108            ErrorHandler handler = MoMLParser.getErrorHandler();
109            MoMLParser.setErrorHandler(null);
110            try {
111                // Parse the momlToBeChecked.
112                parsedContainer = (TypedCompositeActor) parser.parse(
113                        "<entity name=\"auto\" class=\"ptolemy.actor.TypedCompositeActor\">"
114                                + _variableBuffer.toString() + momlToBeChecked
115                                + "</entity>");
116                doParse = false;
117            } catch (MissingClassException ex1) {
118                try {
119                    doParse = _findMissingClass(ex1, container,
120                            parsedContainer);
121                } catch (Exception ex1a) {
122                    return _variableBuffer.toString();
123                }
124            } catch (IllegalActionException ex2) {
125                try {
126                    doParse = _findUndefinedConstantsOrIdentifiers(ex2,
127                            container, parsedContainer, hideVariables);
128
129                } catch (Throwable throwable) {
130                    return _variableBuffer.toString();
131                }
132            } catch (Exception ex3) {
133                throw new IllegalActionException(container, ex3,
134                        "Failed to parse contents of copy buffer.");
135            } finally {
136                MoMLParser.setErrorHandler(handler);
137            }
138        }
139
140        // Iterate through all the entities and attributes, find the attributes
141        // that are variables, validate the variables and look for
142        // errors.
143        if (parsedContainer != null) {
144            // parsedContainer might be null if we failed to parse because
145            // of a missing class
146            Iterator entities = parsedContainer.deepEntityList().iterator();
147            while (entities.hasNext()) {
148                Entity entity = (Entity) entities.next();
149                List<Attribute> entityAttributes = new LinkedList<Attribute>(
150                        entity.attributeList());
151                for (Attribute attribute : entityAttributes) {
152                    _recursiveFindUndefinedConstantsOrIdentifiesInAttribute(
153                            attribute, container, parsedContainer,
154                            hideVariables);
155                }
156            }
157
158            List<Attribute> allAttributes = new LinkedList<Attribute>(
159                    parsedContainer.attributeList());
160            for (Attribute attribute : allAttributes) {
161                _recursiveFindUndefinedConstantsOrIdentifiesInAttribute(
162                        attribute, container, parsedContainer, hideVariables);
163            }
164        }
165
166        return _variableBuffer.toString();
167    }
168
169    ///////////////////////////////////////////////////////////////////
170    ////                         private methods                   ////
171
172    /** Recursively search through an attribute and its contained attributes to
173     *  find any unresolved references to other attributes.
174     *  @param attribute The attribute to be traversed.
175     *  @param container The original container of the attribute.
176     *  @param parsedContainer The temporary container from which the new copied
177     *   unresolved attributes will be generated.
178     *  @param hideVariables If true, add MoML that will make all the found
179     *   variables hidden from the user interface when they are copied.
180     *  @exception IllegalActionException If there is a problem parsing
181     *   an attribute, or validating a variable.
182     */
183    private void _recursiveFindUndefinedConstantsOrIdentifiesInAttribute(
184            Attribute attribute, NamedObj container,
185            TypedCompositeActor parsedContainer, boolean hideVariables)
186            throws IllegalActionException {
187
188        if (attribute instanceof Variable) {
189            Variable variable = (Variable) attribute;
190
191            boolean doGetToken = true;
192            while (doGetToken) {
193                doGetToken = false;
194                try {
195                    variable.getToken();
196                } catch (IllegalActionException ex) {
197                    doGetToken = _findUndefinedConstantsOrIdentifiers(ex,
198                            container, parsedContainer, hideVariables);
199                }
200            }
201            ;
202        }
203
204        // Parse all the StringAttributes and Parameters and
205        // look for missing things.  Note that Expression.expression
206        // is a StringAttribute, so we pick that up here.
207        if (attribute instanceof AbstractSettableAttribute) {
208            AbstractSettableAttribute settable = (AbstractSettableAttribute) attribute;
209            PtParser ptParser = new PtParser();
210            ASTPtRootNode parseTree = null;
211            try {
212                parseTree = ptParser
213                        .generateParseTree(settable.getExpression());
214            } catch (Throwable throwable) {
215                // Skip things we can't parse, like StringAttributes
216                // that are docs.
217
218                // FIXME: we could be smarter here and look
219                // for Expression.expression and only parse
220                // it.  However, this would mean that this
221                // class then dependend on
222                // actor.lib.Expression.  A better design
223                // would be to have a marker interface
224                // implemented by Expression.expression and
225                // search for that interface.
226
227            }
228
229            if (parseTree != null) {
230                ParseTreeFreeVariableCollector variableCollector = new ParseTreeFreeVariableCollector();
231                Set set = variableCollector.collectFreeVariables(parseTree,
232                        /*scope*/null);
233                for (Iterator elements = set.iterator(); elements.hasNext();) {
234                    String name = (String) elements.next();
235
236                    // Look for the variable in parsedContainer
237                    if (parsedContainer.getAttribute(name) == null) {
238                        _findUndefinedConstantsOrIdentifiers(name, name,
239                                container, parsedContainer, hideVariables);
240                    }
241                }
242            }
243        }
244
245        List<Attribute> containedAttributes = attribute.attributeList();
246        for (Attribute containedAttribute : containedAttributes) {
247            _recursiveFindUndefinedConstantsOrIdentifiesInAttribute(
248                    containedAttribute, container, parsedContainer,
249                    hideVariables);
250        }
251    }
252
253    /** Given a MissingClassException, find missing classes.
254     *  @param exception The MissingClassException that contains the class
255     *   that needs to be found.
256     *  @param container The original container of the elements being copied.
257     *  @param parsedContainer The temporary container from which the new copied
258     *   unresolved attributes will be generated.
259     *  @return true if the outer parse should be rerun, false otherwise.
260     */
261    private boolean _findMissingClass(MissingClassException exception,
262            NamedObj container, TypedCompositeActor parsedContainer) {
263
264        // True if we should rerun the outer parse
265        boolean doRerun = false;
266
267        if (container instanceof CompositeEntity) {
268            Iterator containedClasses = ((CompositeEntity) container)
269                    .classDefinitionList().iterator();
270
271            while (containedClasses.hasNext()) {
272                NamedObj containedObject = (NamedObj) containedClasses.next();
273                String missingClassName = exception.missingClassName();
274                if (missingClassName.equals(containedObject.getName())
275                        || missingClassName.startsWith(".")
276                                && missingClassName.substring(1)
277                                        .equals(containedObject.getName())) {
278                    try {
279                        String moml = containedObject.exportMoML().replaceFirst(
280                                "<class", "<class createIfNecessary=\"true\"");
281
282                        MoMLChangeRequest change = new MoMLChangeRequest(
283                                parsedContainer, parsedContainer, moml);
284
285                        if (parsedContainer != null) {
286                            // If we are parsing the moml for the first
287                            // time, then the parsedContainer might be null.
288                            parsedContainer.requestChange(change);
289                        }
290                        _variableBuffer.append(moml);
291                        // Rerun the parse in case there are other problems.
292                        doRerun = true;
293                    } catch (Throwable ex2) {
294                        // Ignore and hope the user pastes into a
295                        // location where the variable is defined
296                    }
297                }
298            }
299        }
300        return doRerun;
301    }
302
303    /** Given an UndefinedConstantOrIdentifierException, find
304     *  missing variables.
305     *  @param exception The UndefinedConstantOrIdentifierException that
306     *   contains the identifier that needs to be found.
307     *  @param container The original container of the elements being copied.
308     *  @param parsedContainer The temporary container from which the new copied
309     *   unresolved attributes will be generated.
310     *  @param hideVariables If true, add MoML that will make all the found
311     *   variables hidden from the user interface when they are copied.
312     *  @return true if the outer parse should be rerun, false otherwise.
313     *  @exception IllegalActionException If there is a problem finding
314     *   the undefined constants or identifiers.
315     */
316    private boolean _findUndefinedConstantsOrIdentifiers(
317            IllegalActionException exception, NamedObj container,
318            TypedCompositeActor parsedContainer, boolean hideVariables)
319            throws IllegalActionException {
320        // True if we should rerun the outer parse or getToken
321
322        // Ok, we have a variable that might have an appropriate
323        // undefined constant or identifier.
324
325        // If the current exception is appropriate, or its cause is
326        // appropriate, then we look for the missing variable.  If the
327        // exception or its cause does not have the node name, then we
328        // can't do anything.
329
330        UndefinedConstantOrIdentifierException idException = null;
331        if (exception instanceof UndefinedConstantOrIdentifierException) {
332            idException = (UndefinedConstantOrIdentifierException) exception;
333        } else {
334            Throwable cause = exception.getCause();
335            while (cause instanceof IllegalActionException) {
336                if (cause instanceof UndefinedConstantOrIdentifierException) {
337                    idException = (UndefinedConstantOrIdentifierException) cause;
338                    break;
339                }
340                cause = ((IllegalActionException) cause).getCause();
341            }
342        }
343
344        if (idException == null) {
345            // The exception or the cause was not an
346            // UndefinedConstantOrIdentifierException, so we cannot do
347            // anything.
348            return false;
349        }
350
351        // We have an exception that has the name of the missing
352        // variable.
353
354        // Find the variable in the object we are copying.
355
356        // Get the name of the variable without the .auto.
357        String variableName = exception.getNameable1().getFullName()
358                .substring(((NamedObj) exception.getNameable1()).toplevel()
359                        .getName().length() + 2);
360
361        return _findUndefinedConstantsOrIdentifiers(variableName,
362                idException.nodeName(), container, parsedContainer,
363                hideVariables);
364    }
365
366    /** Find the missing variables referred to by the given variable name and
367     *  add MoML code to generate them in the _variableBuffer.
368     *  @param variableName The name of the variable which is the root from
369     *   which any missing references should be found.
370     *  @param nodeName The name of the missing constant or identifier.
371     *  @param container The original container of the elements being copied.
372     *  @param parsedContainer The temporary container from which the new copied
373     *   unresolved attributes will be generated.
374     *  @param hideVariables If true, add MoML that will make all the found
375     *   variables hidden from the user interface when they are copied.
376     *  @return true if the outer parse should be rerun, false otherwise.
377     *  @exception IllegalActionException If there is a problem finding
378     *   the undefined constants or identifiers or generating the MoML code for
379     *   the _variableBuffer.
380     */
381    private boolean _findUndefinedConstantsOrIdentifiers(String variableName,
382            String nodeName, NamedObj container,
383            TypedCompositeActor parsedContainer, boolean hideVariables)
384            throws IllegalActionException {
385        boolean doRerun = false;
386
387        Attribute masterAttribute = container.getAttribute(variableName);
388        if (masterAttribute == null) {
389            // Needed to find Parameters that are up scope
390            NamedObj searchContainer = container;
391            while (searchContainer != null && masterAttribute == null) {
392                masterAttribute = searchContainer.getAttribute(variableName);
393                searchContainer = searchContainer.getContainer();
394            }
395        }
396        if (masterAttribute instanceof Variable) {
397            Variable masterVariable = (Variable) masterAttribute;
398            ParserScope parserScope = masterVariable.getParserScope();
399            if (parserScope instanceof ModelScope) {
400                Variable node = masterVariable.getVariable(nodeName);
401
402                if (node == null) {
403                    // Needed when we are copying a composite that contains
404                    // an Expression that refers to an upscope Parameter.
405                    node = masterVariable;
406                }
407
408                if (node == _previousNode) {
409                    // We've already seen this node, so stop
410                    // looping through the getToken() loop.
411                    return false;
412                }
413                _previousNode = node;
414
415                try {
416                    String moml = node.exportMoML().replaceFirst("<property",
417                            "<property createIfNecessary=\"true\"");
418
419                    if (hideVariables) {
420                        moml = _insertHiddenMoMLTagIntoProperty(moml);
421                    }
422
423                    // Insert the new variable so that other
424                    // variables may use it.
425
426                    MoMLChangeRequest change = new MoMLChangeRequest(
427                            parsedContainer, parsedContainer, moml);
428
429                    if (parsedContainer != null) {
430                        // If we are parsing the moml for the first
431                        // time, then the parsedContainer might be null.
432                        parsedContainer.requestChange(change);
433                    }
434                    _variableBuffer.append(moml);
435
436                    // Rerun the getToken() call in case there are
437                    // other problem variables.
438                    doRerun = true;
439                } catch (Throwable ex2) {
440                    // Ignore and hope the user pastes into a
441                    // location where the variable is defined
442                }
443            }
444        }
445        return doRerun;
446    }
447
448    /** Given a MoML string that represents an attribute, insert a MoML tag
449     *  that will hide this attribute from the user interface.
450     *  @param moml The original MoML string that should represent a Ptolemy
451     *   attribute with the &lt;property&gt;&lt;/property&gt; tag.
452     *  @return A new MoML string with the hidden attribute tag inserted into
453     *   the original string.
454     */
455    private String _insertHiddenMoMLTagIntoProperty(String moml) {
456        String hiddenMoML = "<property name=\"style\" class=\"ptolemy.actor.gui.style.HiddenStyle\"></property>";
457        return moml.replaceFirst("</property>", hiddenMoML + "</property>");
458    }
459
460    ///////////////////////////////////////////////////////////////////
461    ////                         private variables                 ////
462
463    /** The previous node for which we searched.  We keep track of
464     *  this to avoid infinite loops.
465     */
466    private Variable _previousNode;
467
468    /** The moml of any missing variables we have found thus far. */
469    private StringWriter _variableBuffer;
470}