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 <property></property> 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}