/*

Copyright: All contributers to the Umple Project

This file is made available subject to the open source license found at:
http://umple.org/license

This file analyze class related umple meta model tokens such class declarations,
associations and attributes and populates the umple meta model.

Please refer to UmpleInternalParser.ump for more details.

 */

namespace cruise.umple.compiler;

class UmpleInternalParser
{
  depend java.util.regex.*;
  
  internal AssociationVariable[] unlinkedAssociationVariables;
  internal Association[] unlinkedAssociations;
  internal Map<Position,String> positionToClassNameReference = new HashMap<Position, String>();
  internal Map<UmpleClassifier,List<String>> unlinkedExtends = new HashMap<UmpleClassifier,List<String>>();
  internal Map<UmpleClassifier,List<Token>> unlinkedExtendsTokens = new HashMap<UmpleClassifier,List<Token>>();
  internal Map<UmpleClassifier,List<Token>> unlinkedExtendsTokensInterface = new HashMap <UmpleClassifier, List<Token>>();
  internal Map<UmpleClassifier,List<String>> unlinkedInterfaceExtends = new HashMap<UmpleClassifier,List<String>>();

  // List of comments populated by the parser to be added to the next class, attribute, association, method, etc.
  internal List<Comment> lastComments = new ArrayList<Comment>();

  /*
   * Analyze class definitions and related tokens.
   * 
   * @param t The current token that will be analyzed to determine how to further make use of it (is it a comment, class, etc?)
   * @param analysisStep Used to determine whether or not things should be analyzed more than once (multi-pass).
   */
  private void analyzeClassToken(Token t, int analysisStep)
  {
    if (analysisStep != 2)
    {
      shouldProcessAgain = shouldProcessAgain || (analysisStep == 1);
      return;
    }

    // Only need to clear comments if there actually was comments.
    boolean shouldConsumeComment = lastComments.size() > 0;

    // Determine what the current token is primarily, and based on that the analysis procedure is determined.
    if (t.isStatic("//") || t.isStatic("/*") || t.isStatic("*/"))
    {
      shouldConsumeComment = false;
    }
    else if (t.is("strictness") )
    {
      // unimplemented feature. Issue a warning that it is currently not fully implemented
      setFailedPosition(t.getPosition(), 9999, t.getName(), t.toString());
    }
    else if (t.is("strictnessMessage"))
    {
      if(t.getValue("message").equals("allow"))
      {
        allowMessage(Integer.parseInt(t.getValue("messageNumber")));
      }
      else if(t.getValue("message").equals("disallow"))
      {
        disallowMessage(Integer.parseInt(t.getValue("messageNumber")));
      }
      else if(t.getValue("message").equals("ignore"))
      {
        ignoreMessage(Integer.parseInt(t.getValue("messageNumber")));
      }
      else if(t.getValue("message").equals("expect"))
      {
        expectMessage(Integer.parseInt(t.getValue("messageNumber")));
      }
    }      
    else if (t.is("namespace"))
    {
      if(!packageNameUsed && !t.getValue().equals(currentPackageName))
        setFailedPosition(t.getPosition(),31,currentPackageName,t.getValue());
      currentPackageName = t.getValue();
      if (model.getDefaultNamespace() == null)
      {
        model.setDefaultNamespace(currentPackageName);  
      }
      packageNameUsed = false;
    }
    else if (t.is("inlineComment"))
    {
      analyzeComment(t);
      shouldConsumeComment = false;
    } 
    else if (t.is("multilineComment"))
    {
      analyzeMultilineComment(t);
      shouldConsumeComment = false;
    }
    else if (t.is("classDefinition"))
    {
      analyzeClass(t);
    }
    else if (t.is("externalDefinition"))
    {
      if (t.getValue("interface")!=null)
        analyzeExternalInterface(t);
      else
        analyzeExternal(t);
    }
    else if (t.is("interfaceDefinition"))
    {
      analyzeInterface(t);
    }
    else if (t.is("associationClassDefinition"))
    {
      analyzeAssociationClass(t);
    }
    else if (t.is("associationDefinition"))
    {
      analyzeAllAssociations(t);
    }

    // This essentially "clears" the comments in the list so that new comments, when parsed, will be the ones appearing above
    // classes, methods, attributes, etc (whichever comes next) rather than old comments propogating everywhere.
    if (shouldConsumeComment)
    {
      lastComments.clear();
    }

  }  

  /*
   * Analyze class content tokens.
   * 
   * @param token The current token that will be analyzed to determine how to further make use of it (is it a method, comment, 
   * attribute, etc?)
   * @param aClass The Umple class used such that parsed content such as methods, attributes, comments, etc may be added to
   * it.
   * @param analysisStep Used to determine whether or not things should be analyzed more than once (multi-pass).
   */
  private void analyzeClassToken(Token token, UmpleClass aClass, int analysisStep)
  {
    if (analysisStep != 1)
    {
      return;
    }

    // Only need to clear comments if there actually was comments.
    boolean shouldConsumeComment = lastComments.size() > 0;
    


    // Determine what the current token is primarily, and based on that the analysis procedure is determined.
    if (token.isStatic("//") || token.isStatic("/*") || token.isStatic("*/"))
    {
      shouldConsumeComment = false;
    }
    else if (token.is("inlineComment"))
    {
      analyzeComment(token);
      shouldConsumeComment = false;
    }
    else if (token.is("multilineComment"))
    {
      analyzeMultilineComment(token);
      shouldConsumeComment = false;
    }
    // TODO Under development
    
    else if (token.is("classDefinition"))
    {
      UmpleClass childClass = analyzeClass(token);
      boolean wasSet = childClass.setExtendsClass(aClass);
      if (!wasSet)
      {
        setFailedPosition(token.getPosition(), 16, childClass.getName(), aClass.getName());
      }
    }
    else if (token.is("attribute"))
    {
      analyzeAttribute(token,aClass);
    }
    else if (token.is("invariant"))
    {
      analyzeInvariant(token,aClass);
      // unimplemented feature. Issue a warning that it is currently not fully implemented
      // setFailedPosition(token.getPosition(), 9999, token.getName(), token.toString());
    } 
    else if (token.is("beforeCode") || token.is("afterCode"))
    {
      
      analyzeInjectionCode(token,aClass);
    }
    else if (token.is("key") || token.is("defaultKey")) 
    {
      analyzeKey(token,aClass);
    }
    else if (token.is("extraCode"))
    {
      //This is a catch all and will be used less often as the grammar gets updated.
      if(extraCodeIsMalformedStateMachine(token)) setFailedPosition(token.getPosition(), 1006, "");
      //Append #line comment to indicate line and position of source
      if (token.getPosition() != null)
      {
        aClass.appendExtraCode("// line " + token.getPosition().getLineNumber() + " " + token.getPosition().getRelativePath(aClass, "Java"));
        aClass.appendExtraCode("  " + token.getValue());
      }
      else
      {
        aClass.appendExtraCode(token.getValue());
      }
    }
    else if (token.is("constantDeclaration"))
    {
      analyzeConstant(token,aClass);
    }
    else if(token.is("constantDeclarationDeprecated"))
    {
      setFailedPosition(token.getPosition(), 901);
      analyzeConstant(token,aClass);
    }
    else if (token.is("concreteMethodDeclaration"))
    {
      analyzeMethod(token,aClass);
    }
    else if (token.is("depend"))
    {
      Depend d = new Depend(token.getValue());
      aClass.addDepend(d);
    }
    else if (token.is("inlineAssociation"))
    {
      analyzeinlineAssociation(token,aClass);
    }
    else if (token.is("symmetricReflexiveAssociation"))
    {
      analyzeSymmetricReflexiveAssociation(token,aClass);
    }    
    else if (token.is("exception"))
    {
      analyzeException(token,aClass);
    }

    // This essentially "clears" the comments in the list so that new comments, when parsed, will be the ones appearing above
    // classes, methods, attributes, etc (whichever comes next) rather than old comments propogating everywhere.
    if (shouldConsumeComment)
    {
      lastComments.clear();
    }
  }    

  /**
   * Analyzes a comment to determine if it should be added into the list of currently parsed comments waiting to be added to
   * a class, attribute, association, method or otherwise.
   * 
   * Note that this is for an inline comment rather than a multiline comment.
   * 
   * @param token The current token which has been flagged to be a comment to analyze, containing its value.
   */
  private void analyzeComment(Token token)
  {
    String theValue = "";
    // Special comment directive to force umpleoutput directives to be added
    // In every class
    if (token.getValue().startsWith("@outputumplesource")) { 
      outputUmpleSource = true;
      return;
    }
    if (!token.getValue().equals("$?[End_of_model]$?")) 
    {
      theValue = token.getValue();
      lastComments.add(new Comment(theValue));
    }
  }

  /**
   * Analyzes a comment to determine if it should be added into the list of currently parsed comments waiting to be added to
   * a class, attribute, association, method or otherwise.
   * 
   * Note that this is for a multiline comment, which essentially means the possibility of multiple inline comments (1 per line)
   * that will be concatenated together.
   * 
   * @param token The current token which has been flagged to be a comment to analyze, containing its value.
   */
  private void analyzeMultilineComment(Token token)
  {
    String inlineComments[] = token.getValue().split("\n");

    // Go through the inline comments and add them to the list of comments waiting to be applied
    String theComment = "";
    for (int i = 0; i < inlineComments.length; i++) 
    {
      theComment = inlineComments[i];
      if(theComment.startsWith("@outputumplesource")) {
        outputUmpleSource = true;
      }
      else {
        Comment comment = new Comment(theComment);
        comment.setIsInline(false);
        lastComments.add(comment);
      }
    }
  }

  // Link associations, association variables and extends that were "defined" after their use
  private void postTokenClassAnalysis()
  {
    if (verifyClassesInUse())
    {
      checkSingletonAssociations();
      addUnlinkedAssociationVariables();
      addUnlinkedAssociations();
      addUnlinkedExtends();
      checkDuplicateAssociationNames();
      checkExtendsForCycles();
      checkSortedAssociations();
      checkClassInterfaceAssocations();
      checkExtendsClass();
    }
  }

  private void postTokenInterfaceAnalysis()
  {
    addUnlinkedInterfaceExtends();
    checkExtendsForCyclesInterface();
  }
  
   private List <UmpleInterface> recursiveCycleCheckInterface(List <UmpleInterface> extend,UmpleInterface parent, HashMap<UmpleInterface, Boolean> map)
  {

    List <UmpleInterface> temp = new ArrayList <UmpleInterface>();

    if(extend == null|| extend.isEmpty())
      return temp;


    for (UmpleInterface I: extend){
      if(map.containsKey(I)){
        temp.add(I);
        return temp;
      }
      map.put(I, true);    

      for (UmpleInterface EI: I.getExtendsInterface()){
        if(parent.equals(EI)){
          temp.add(EI);
          return temp;
        }  
      }
      List <UmpleInterface> temptemp = recursiveCycleCheckInterface(I.getExtendsInterface(), parent, map);
      temp.addAll(temptemp);
    }  

    return temp;
  }

  private void checkExtendsForCyclesInterface()
  {
    for(UmpleInterface I : model.getUmpleInterfaces())
    {
      HashMap<UmpleInterface, Boolean> vistedMap = new HashMap<UmpleInterface, Boolean>();
      if(I.getExtendsInterface() != null)
      {
        if(recursiveCycleCheckInterface(I.getExtendsInterface(), I, vistedMap).contains(I)){
          Token t = I.getExtendsToken();
              if(t.getValue().equals(I.getName()))
                getParseResult().addErrorMessage(new ErrorMessage(11,t.getPosition(),"Interface",I.getName()));
              else
                getParseResult().addErrorMessage(new ErrorMessage(12,t.getPosition(),"Interface",t.getValue(),I.getName()));
        }
      }
    }
  }

  private UmpleClass recursiveCycleCheck(UmpleClass extend, UmpleClass parent, HashMap<UmpleClass, Boolean> map)
  {
    UmpleClass temp = null;

    if(extend == null)
      return null;

    if(map.containsKey(extend))
      return extend;

    map.put(extend, true);

    if(parent.equals(extend.getExtendsClass()))
      return extend.getExtendsClass();

    temp = recursiveCycleCheck(extend.getExtendsClass(), parent, map);
    return temp;
  }

  private void checkExtendsForCycles()
  {
    for(UmpleClass C : model.getUmpleClasses())
    {
      HashMap<UmpleClass, Boolean> vistedMap = new HashMap<UmpleClass, Boolean>();
      if(C.getExtendsClass() != null)
      {
        if(C.equals(recursiveCycleCheck(C.getExtendsClass(), C, vistedMap))) 
        {
          Token t = C.getExtendsToken();
          if(t.getValue().equals(C.getName()))
            getParseResult().addErrorMessage(new ErrorMessage(11,t.getPosition(),"Class",C.getName()));
          else
            getParseResult().addErrorMessage(new ErrorMessage(12,t.getPosition(),"Class",t.getValue(),C.getName()));
        }
      }
    }
  }

  // Check for the existence of a a parent class
  private void checkExtendsClass(){
    for(UmpleClass child : model.getUmpleClasses()) {
      if(child.getExtendsToken() != null)
        if (child.getExtendsClass() != null )
          continue;
      else{
        Token t = child.getExtendsToken();
        getParseResult().addErrorMessage(new ErrorMessage(33,t.getPosition(),t.getValue(),
          child.getName()));      
      }
    }
  }

  /*
   * Analyzes all associations that are part of the given token indicated to be related to an association.
   * 
   * @param associationToken The token indicated to be an association or association Class where sub tokens will be analyzed from to further
   * analyze the individual associations.
   */
  private void analyzeAllAssociations(Token associationToken)
  {
    String name = associationToken.getValue("name");

    // Go through every token that is a child of the current token (all associations part of this association).
    for(Token token : associationToken.getSubTokens()){
      boolean isAssociationToken = token.is("association");

      //Issue 213/131: [association] elements inside associationClasses generate 2 associations instead of one
      if (isAssociationToken && associationToken.is("associationClassDefinition")) {
        for (Token t : token.getSubTokens()) {
          if (t.is("associationEnd")) {
            analyzeAssociation(t, "");
          }
        }
      } else if (isAssociationToken || token.is("singleAssociationEnd")){
        Association association = analyzeAssociation(token, "");
        if(isAssociationToken && association != null){
          association.setName(name);
        }
      }
      if (!getParseResult().getWasSuccess()) { return; }
    }
  }

  /*
   * Analyzes a class token to populate an Umple class.
   * 
   * This is also where the list of currently parsed comments will be added to the Umple class.
   * 
   * @param classToken The token which contains the data to be analyzed to populate an Umple class.
   * 
   * @return An Umple class populated with data based on the analysis of the class token.
   */
  private UmpleClass analyzeClass(Token classToken)
  {
    String className = classToken.getValue("name").split(" ")[classToken.getValue("name").split(" ").length-1];
    //Check to ensure the name is valid (starts with a letter, and only contains letters, numbers, or underscores
    if (Token.isValidIdentifier(className, "[A-Za-z]") != true) {
      setFailedPosition(classToken.getPosition(), 100, className);
    }
    else if ( className.matches("[a-z].*") ){ // Warn when class name does not start with a capital letter.
      setFailedPosition(classToken.getPosition(), 101, className);
    }
    UmpleClass aClass;
    //Issue 213: UmpleClass can be an AssociationClass
    if(classToken.is("associationClassDefinition")){
        aClass = model.addAssociationClass(classToken.getValue("name"));
    }else{
        aClass = model.addUmpleClass(classToken.getValue("name"));
    }
    if ( classToken.is("classDefinition") && "external".equals(aClass.getModifier()) )
      aClass.setModifier(""); // Remove the external modifier if a non-external specification of this class is found.

    Position thePosition = classToken.getPosition();
    Position endPosition = classToken.getEndPosition();

    // Set the original .ump file and line number
    aClass.addPosition(thePosition);
    aClass.addEndPosition(endPosition);

    // Add all the comments in the comment list to the Umple class.
    // But add them before any umplesource special comments
    int regularCommentCountEnd = 0;
    for (Comment c : aClass.getComments()) {
      if(c.getText().startsWith("@umplesource")) break;
      regularCommentCountEnd++;
    }

    for (Comment c : lastComments)
    {
      aClass.addCommentAt(c,regularCommentCountEnd);
      regularCommentCountEnd++;
    }
    
    // Add special position comment at the end if @outputumplesource had been 
    // detected earlier in a comment
    if(outputUmpleSource == true) {  
      aClass.addComment(new Comment("@umplesource " + thePosition.getRelativePath(null,"Java")+" "+thePosition.getLineNumber()));
    }

  // If the "abstract" keyword is parsed, make the Umple class an abstract class.
    if (classToken.getValue("abstract") != null)
    {
      boolean wasSet = aClass.setIsAbstract(true);
      
      // Ensure the value was set.
      if (wasSet == false)
      {
        setFailedPosition(classToken.getPosition(), 0, "Unable to make class abstract!");
      }
    }

    addExtendsTo(classToken, aClass, unlinkedExtends, unlinkedExtendsTokens);
    
    // If the "singleton" keyword is parsed, make the Umple class a singleton.
    if (classToken.getValue("singleton") != null)
    {
      aClass.setIsSingleton(true);
    }
    if(!"".equals(aClass.getPackageName()) && !currentPackageName.equals(aClass.getPackageName()) && !packageNameUsed){
      setFailedPosition(classToken.getPosition(), 30, aClass.getName(), currentPackageName);
      aClass.setPackageName(currentPackageName);    
    }    
    if("".equals(aClass.getPackageName())){
      aClass.setPackageName(currentPackageName);
  }
  packageNameUsed = true;
    if (aClass.getIsSingleton()) 
    {
      classToken.setName(classToken.getName());  
    }

    if (classToken.getValue("immutable") != null)
    {
      boolean wasSet = aClass.setImmutable();
      if (!wasSet)
      {
        // Future-proofing: currently all paths cause wasSet to be true
        setFailedPosition(classToken.getPosition(), 14, classToken.getName());
      }
    }

    analyzeAllTokens(classToken,aClass);
    return aClass;
  }

  /*
   * Takes an Umple classifier and analyzes a classifier token to add classifiers which extend it.
   * 
   * @param classifierToken The token to be analyzed to add subclasses to the specified Umple classifier.
   * @param aClassifier The Umple classifier for which subclasses will be added.
   */
  private void addExtendsTo(Token classifierToken, UmpleClassifier aClassifier, Map <UmpleClassifier,List <String>> unlinkedExtends, Map <UmpleClassifier, List<Token>> unlinkedExtendsTokens)
  //private void addExtendsTo(Token classToken, UmpleClassifier aClassifier)
  {
    List<String> extendsList = new ArrayList<String>();
    List<Token> extendsTokenList = new ArrayList<Token>();

    // Go through all sub-tokens of the class token to add subclasses related to the Umple class.
    for (Token extendsToken : classifierToken.getSubTokens())
    {
      if (extendsToken.getValue("extendsName") != null)
      { 
        extendsList.add(extendsToken.getValue("extendsName"));
        extendsTokenList.add(extendsToken); // With this line we really don't need the above, todo: refactor
        unlinkedExtends.put(aClassifier, extendsList);
        unlinkedExtendsTokens.put(aClassifier, extendsTokenList);
      }  
    }
  	//Checks list of extends tokens to prevent multiple class inheritance
		if(numberOfExtendsClass(extendsTokenList) > 1)
		{
			Token t = extendsTokenList.get(0);			
			getParseResult().addErrorMessage(new ErrorMessage(34,t.getPosition(),aClassifier.getName(),t.getValue()));
		}
   }
	
	//Returns the number of umple class in extends list (extList)
  private int numberOfExtendsClass(List<Token> extList)
	{
		int counter = 0;
		for(Token t : extList)
		{	
			if(isAnUmpleClass(t.getValue("extendsName")))
				counter++;
		}
		return counter;
	}

	//This method checks if an umple element with name "name" is an umple class
	private boolean isAnUmpleClass(String name)
	{
		for(UmpleClass aClass : model.getUmpleClasses())
		{
			if(aClass != null)
			{
				String nam = aClass.getName();
				if(nam.equals(name))
					return true;
			}
		}
		return false;
	}
  private UmpleClass analyzeExternal(Token externalToken)
  {
    // Check to see if there is an existing class
    UmpleClass existingClass = model.getUmpleClass(externalToken.getValue("name"));
    UmpleClass aClass = analyzeClass(externalToken);
    // Only set the modifier to external if there is not a class defined with the same name
    if ( existingClass == null )
      aClass.setModifier("external");
    return aClass;
  }

  private UmpleInterface analyzeExternalInterface(Token externalToken)
  {
    UmpleInterface anInterface = analyzeInterface(externalToken);
    anInterface.setModifier("external");
    return anInterface;
  }

  private UmpleInterface analyzeInterface(Token t)
  {
    String interfaceName = t.getValue("name");   
    //Check to ensure the name is valid (starts with a letter, and only contains letters, numbers, or underscores
    if (Token.isValidIdentifier(interfaceName, "[A-Za-z|@]") != true) {
      setFailedPosition(t.getPosition(), 110, interfaceName);
    }
    else if ( interfaceName.matches("[a-z].*") ){ // Warn when interface name doesn't start with a capital letter.
       setFailedPosition(t.getPosition(), 111, interfaceName);
    }
  
    UmpleInterface newInterface = new UmpleInterface(t.getValue("name"));
    model.addUmpleInterface(newInterface);
    if(!"".equals(newInterface.getPackageName()) && !currentPackageName.equals(newInterface.getPackageName()) && !packageNameUsed){
      setFailedPosition(t.getPosition(), 30, newInterface.getName(), currentPackageName);
      newInterface.setPackageName(currentPackageName);    
    }    
    if("".equals(newInterface.getPackageName())){
      newInterface.setPackageName(currentPackageName);
  }
  packageNameUsed = true;
    analyzeInterface(t,newInterface);
    return newInterface;
  }

  private void analyzeInterface(Token interfaceToken, UmpleInterface aInterface)
  {
    for(Token token : interfaceToken.getSubTokens())
    {
      if (token.is("depend"))
      {
        Depend d = new Depend(token.getValue());
        aInterface.addDepend(d);
      }
      if (token.is("interfaceMemberDeclaration"))
      {
        analyzeInterfaceMembers(token, aInterface);
      }
      else if (token.is("elementPosition"))
      {
        aInterface.setCoordinates(new Coordinate(token.getIntValue("x"),token.getIntValue("y"), token.getIntValue("width"), token.getIntValue("height")));
      }
      else if (token.is("displayColor"))
      {  // Note: See near clone in UmpleInternalParser_CodeLayout.ump
        String theColor = token.getValue("colorValue");
        if(theColor.startsWith("=")) theColor=theColor.substring(1,theColor.length());
        if(!theColor.startsWith("\"")) theColor= "\""+theColor;
        if(!theColor.endsWith("\"")) theColor= theColor+"\"";         
        aInterface.setDisplayColor(theColor);
      }
    }
  }

  private void addUnlinkedInterfaceExtends()
  {  
    for (UmpleClassifier c : unlinkedInterfaceExtends.keySet())
    {
      UmpleInterface child = null; //unlinkedInterfaceExtends guaranteed to contain only UmpleInterfaces
      if (c instanceof UmpleInterface){
        child = (UmpleInterface) c;
      }
      List<String> extendsNames = unlinkedInterfaceExtends.get(child);
      List<Token>  extendsToken = unlinkedExtendsTokensInterface.get(child);
      

      if (extendsNames == null)
      {
        continue;
      }
      

      for (int i=0; i < extendsNames.size();i++)
      {
        String extendName= extendsNames.get(i);
        UmpleInterface uInterface=  model.getUmpleInterface(extendName);
        boolean wasSet = child.addExtendsInterface(uInterface);
        
        if (!wasSet)
        {
          Position pos;
          try
          {
            pos = extendsToken.get(i).getPosition();
          }
          catch(Exception e)
          {
            pos = new Position("",0,0,0);
          }
          setFailedPosition(pos, 16, child.getName(), uInterface.getName());
          return;
        }
        try
        {
          child.setExtendsToken(extendsToken.get(i));
        }
        
        catch(Exception e){}
      }
    }
  }  

  private void analyzeInterfaceMembers(Token interfaceMemberToken, UmpleInterface aInterface)
  {
    for(Token childToken : interfaceMemberToken.getSubTokens())
    {
      addExtendsTo(interfaceMemberToken, aInterface, unlinkedInterfaceExtends, unlinkedExtendsTokensInterface);
      if(childToken.is("abstractMethodDeclaration"))
      {
        analyzeMethod(childToken, aInterface);   
      }  
      else if (childToken.is("constantDeclaration"))
      {
        analyzeConstant(childToken, aInterface);    
      }
      else if (childToken.is("constantDeclarationDeprecated"))
      {
        setFailedPosition(childToken.getPosition(), 901);
        analyzeConstant(childToken, aInterface);    
      }
      else if (childToken.is("extraCode"))
      {
        aInterface.appendExtraCode(childToken.getValue("extraCode"));
      }
    }
  }

  private void analyzeAssociationClass(Token classToken)
  {
    //test if Association class has at least 1 association or more than one singleEndAssociation
    List<Token> subtokens = classToken.getSubTokens();
    int singleAssocNumber = 0;
    int assocNumber = 0;
    for(Token t : subtokens){
        if(t.is("singleAssociationEnd")){
            singleAssocNumber++;
        }else if (t.is("association")){
            assocNumber++;
        }
    }
    if(singleAssocNumber == 1 || (assocNumber == 0 && singleAssocNumber == 0)){
        setFailedPosition(classToken.getPosition(), 8, classToken.getValue("name"));
        return;
    }
    analyzeClass(classToken);
    analyzeAllAssociations(classToken);
  }

   private boolean verifyClassesInUse()
  {
    for(Map.Entry<Position, String> e : positionToClassNameReference.entrySet())
    {
      boolean isAClass = model.getUmpleClass(e.getValue()) != null;
      boolean isAInterface = model.getUmpleInterface(e.getValue()) != null;

      if (!isAClass && !isAInterface) //item referenced not a class or interface
      {
        UmpleClass aClass = model.addUmpleClass(e.getValue());
        aClass.setPackageName(model.getDefaultNamespace());
        setFailedPosition(e.getKey(), 5, e.getValue());
        return false;
      }
    }
    
    return true;
  }
  
    private boolean associationIsBetweenClassAndInterface (Association a){
     AssociationEnd myEnd = a.getEnd(0);
       AssociationEnd yourEnd = a.getEnd(1);
       
       UmpleClass myClass = model.getUmpleClass(myEnd.getClassName());
       UmpleInterface yourClass = model.getUmpleInterface(yourEnd.getClassName());
       
       if (myClass != null && yourClass != null ){ //association is between class and interface
         return true;
       }
       
       return false;
      
   }
  


  private void addUnlinkedAssociationVariables()
  {
    for (AssociationVariable av : unlinkedAssociationVariables)
    {
       
      UmpleClass aClass = model.getUmpleClass(av.getType());
      UmpleClass bClass = model.getUmpleClass(av.getRelatedAssociation().getType());   
      
      if (aClass == null || bClass == null){ //Association is between Class and Interface
        continue;
      }   

      Association assoc = bClass.getAssociation(bClass.indexOfAssociationVariable(av));

      boolean added = aClass.addAssociationVariable(av.getRelatedAssociation());
      if (!added)
      {
        if ((!aClass.isImmutable() && !av.getRelatedAssociation().getIsNavigable()) || (!bClass.isImmutable() && !av.getIsNavigable())) 
        { 
          setFailedPosition(assoc.getTokenPosition(),13);
        }
        else { setFailedPosition(assoc.getTokenPosition(),18); }
        return;
      }

      aClass.addAssociation(assoc);

      if (av.getIsNavigable())
      {
        bClass.addReferencedPackage(aClass.getPackageName());
      }

      if (av.getRelatedAssociation().getIsNavigable())
      {
        aClass.addReferencedPackage(bClass.getPackageName());
      }

    }
  }

  private boolean isUmpleClass(String elementName)
  {
    return (model.getUmpleInterface(elementName) != null) ? false: true;
  }

  private void addUnlinkedExtends()
  {  
    for (UmpleClassifier c : unlinkedExtends.keySet())
    {
      UmpleClass child = null; // unlinkedExtends guaranteed to contain only UmpleClasses
      if (c instanceof UmpleClass){
        child = (UmpleClass) c;
      }  
    
      List<String> extendsNames = unlinkedExtends.get(child);    
      List<Token>  extendsToken = unlinkedExtendsTokens.get(child);

      if (extendsNames == null)
      {
        continue;
      }

      for (int i=0; i < extendsNames.size();i++){
        String extendName= extendsNames.get(i);
        if (isUmpleClass(extendName))
        {
          UmpleClass parent = model.getUmpleClass(extendName);     
          boolean wasSet = child.setExtendsClass(parent);
          if (!wasSet)
          {
            Position pos;
            try
            {
              pos = extendsToken.get(i).getPosition();
            }
            catch(Exception e)
            {
              pos = new Position("",0,0,0);
            }
            setFailedPosition(pos, 16, child.getName(), parent.getName());
            return;
          }
          try
          {
            child.setExtendsToken(extendsToken.get(i));
          }
          catch(Exception e){}
        }
        else {
          UmpleInterface uInterface=  model.getUmpleInterface(extendName);
          child.addParentInterface(uInterface);
          addImplementedMethodsFromInterface(uInterface, child);
        }
      }
    }
  }  

  private void addImplementedMethodsFromInterface(UmpleInterface parentInterface, UmpleClass uClass)
  {
    //GET AND SET METHODS CHECK?
    if (parentInterface.hasMethods())
    {
      for (Method aMethod : parentInterface.getMethods())
      {
        boolean shouldAddMethod = verifyIfMethodIsConstructorOrGetSet(uClass, aMethod);
        if (!(uClass.hasMethod(aMethod)) && shouldAddMethod)
        {
          if("".equals(aMethod.getMethodBody().getExtraCode("")))
          {
            aMethod.setIsImplemented(true);
          }
          uClass.addMethod(aMethod);
        }
      }
    }
  }

  /*
   * Used to determine if a method is a contructor or a getter/setter.
   * 
   * @param uClass The Umple class for which the method is contained.
   * @param aMethod The method which is contained within the Umple class.
   * 
   * @return True if the method is a constructor, getter/setter, false otherwise.
   */
  private boolean verifyIfMethodIsConstructorOrGetSet(UmpleClass uClass, Method aMethod)
  {
    String methodName = aMethod.getName();

    // Have to check for short method names.
    if (methodName.length() >= 3)
    {
      //1. Verify if method to be added is a setter or a getter
      String accessorName = methodName.substring(0,3);
      if ((accessorName.equals("get") && aMethod.numberOfMethodParameters()==0) || (accessorName.equals("set")&& aMethod.numberOfMethodParameters()==1))
      {
        String possibleAttributeName =   methodName.substring(3,methodName.length()).toLowerCase();
        Attribute attr = uClass.getAttribute(possibleAttributeName);
        if (attr != null)
        {
          return false;
        }
      }
      //2. Verify if method to be added is a constructor
      if (aMethod.getType().equals("public"))
      {
        uClass.appendExtraCode(aMethod.toString());
        return false;
      }  
      //3. Verify if method from interface is already part of the Class extracode
      String match = "public " + aMethod.getType() + " " + aMethod.getName();    
      if (uClass.getExtraCode().contains(match))
      {
        return false;
      }
    }

    return true;
  }
  
  private void checkDuplicateAssociationNames()
  {
    for(UmpleClass C : model.getUmpleClasses())
    {
      // Create the list of attribute names (for issue 272)
      List<String> existingAttributeNames = new ArrayList<String>();
      for (Attribute attr : C.getAttributes())
      {
              existingAttributeNames.add(attr.getName());
      }
      
      Boolean roleMatchesClassName, hasMultipleAssocToSameClass;
      List<String> classesWithAssociationsToCurrClass = new ArrayList<String>();
      List<String> roleNameSameAsClassName = new ArrayList<String>();
      List<String> existingNames = new ArrayList<String>();
      List<Association> visitedAssociations = new ArrayList<Association>();
      for(Association assoc : C.getAssociations())
      {  
       roleMatchesClassName = false;
       hasMultipleAssocToSameClass = false;
       
        if (visitedAssociations.contains(assoc))
        {
          continue;
        }
        
        AssociationEnd firstEnd = assoc.getEnd(0);
        AssociationEnd secondEnd = assoc.getEnd(1);  
        
        Boolean checkFirstEnd = !firstEnd.getClassName().equals(C.getName());
        Boolean checkSecondEnd = !secondEnd.getClassName().equals(C.getName());
        Boolean associationIsReflexive = !checkFirstEnd && !checkSecondEnd;
        
        //issue 288: firstEnd of association does not indicate current (this) class being analyzed.
        //If association is NOT reflexive, must check the differing class.  Check if role name
        //matches class name, but only if it is a user entered role name.  Current class must
        //also have multiple associations to the same class to cause java compile errors.  
        if(!associationIsReflexive && C.numberOfAssociations() > 1)
        { 
          //check the differing class
          if(checkFirstEnd)
          {
            if(roleNameSameAsClassName.contains(firstEnd.getClassName().toLowerCase()))
            {
              hasMultipleAssocToSameClass = true;   //flag error 19
            }
            //is a user-defined role name and rolename matches class name
            else if(firstEnd.getRoleName().toLowerCase().equals(firstEnd.getClassName().toLowerCase()) && !firstEnd.getIsDefaultRoleName())
            {    
              roleNameSameAsClassName.add(firstEnd.getRoleName().toLowerCase());
            }
            
            classesWithAssociationsToCurrClass.add(firstEnd.getClassName());
          }
          //check the differing class
          if(checkSecondEnd)
          {
            if(roleNameSameAsClassName.contains(secondEnd.getClassName().toLowerCase()))
            {
              hasMultipleAssocToSameClass = true;  //flag error 19
            }
            //is a user-defined role name and rolename matches class name
            else if(secondEnd.getRoleName().toLowerCase().equals(secondEnd.getClassName().toLowerCase()) && !secondEnd.getIsDefaultRoleName())
            {    
              roleNameSameAsClassName.add(secondEnd.getRoleName().toLowerCase());
            }
            
            classesWithAssociationsToCurrClass.add(secondEnd.getClassName());          
          }
        }

        // check names on other-class end of associations to other classes
        if ((checkFirstEnd || associationIsReflexive) && assoc.getIsLeftNavigable())
        { 
          if (existingNames.contains(firstEnd.getRoleName()) || hasMultipleAssocToSameClass)
          {
            getParseResult().addErrorMessage(new ErrorMessage(19,assoc.getTokenPosition(),C.getName(),firstEnd.getRoleName()));
          }
          else if (existingAttributeNames.contains(firstEnd.getRoleName()))
          {  // Check if the association name is the same as an attribute name
             getParseResult().addErrorMessage(new ErrorMessage(23,assoc.getTokenPosition(),C.getName(),firstEnd.getRoleName()));
          }
          else
          {
            existingNames.add(firstEnd.getRoleName());
          }
        }
        if ((checkSecondEnd || associationIsReflexive) && assoc.getIsRightNavigable())
        {
          if (existingNames.contains(secondEnd.getRoleName()) || hasMultipleAssocToSameClass)
          {
            getParseResult().addErrorMessage(new ErrorMessage(19,assoc.getTokenPosition(),C.getName(),secondEnd.getRoleName()));
          }
          else if (existingAttributeNames.contains(secondEnd.getRoleName()))
          {  // Check if the association name is the same as an attribute name
             getParseResult().addErrorMessage(new ErrorMessage(23,assoc.getTokenPosition(),C.getName(),secondEnd.getRoleName()));
          }
          else
          {
            existingNames.add(secondEnd.getRoleName());
          }
        }
        
        if (associationIsReflexive)
        { 
          // The UmpleClass is only expected to have duplicate references to reflexive associations
          visitedAssociations.add(assoc);
        }
      }
    }
  }

  private void checkSingletonAssociations() 
  {
    for (Association association : model.getAssociations()) 
    {  
      if (associationIsBetweenClassAndInterface (association)){continue;}  
      
      AssociationEnd myEnd = association.getEnd(0);
      AssociationEnd yourEnd = association.getEnd(1);

      UmpleClass myClass = model.getUmpleClass(myEnd.getClassName());
      UmpleClass yourClass = model.getUmpleClass(yourEnd.getClassName());

      if (myClass.getIsSingleton() && (yourEnd.getMultiplicity().getRangeParts()[0].equals("1") && yourEnd.getMultiplicity().getRangeParts()[1].equals("1"))) 
      {
        yourEnd.getMultiplicity().setRange("0", "1");
        yourEnd.getMultiplicity().setBound(null);
        setFailedPosition(association.getTokenPosition(), 2, association.getName());  
      }

      if (yourClass.getIsSingleton() && (myEnd.getMultiplicity().getRangeParts()[0].equals("1") && myEnd.getMultiplicity().getRangeParts()[1].equals("1"))) 
      {
        myEnd.getMultiplicity().setRange("0", "1");
        myEnd.getMultiplicity().setBound(null);
        setFailedPosition(association.getTokenPosition(), 2, association.getName());
      }

      if(myClass.getIsSingleton() && (myEnd.getMultiplicity().getUpperBound() < 0 || myEnd.getMultiplicity().getUpperBound() > 1)) 
      {
        setFailedPosition(association.getTokenPosition(), 10, myEnd.getClassName());
      }

      if(yourClass.getIsSingleton() && (yourEnd.getMultiplicity().getUpperBound() < 0 || yourEnd.getMultiplicity().getUpperBound() > 1)) 
      {
        setFailedPosition(association.getTokenPosition(), 10, yourEnd.getClassName());
      }
    }
  }

  private void addUnlinkedAssociations()
  {
    for (Association association : unlinkedAssociations)
    {         
      if (associationIsBetweenClassAndInterface (association)){continue;}  
      
      AssociationEnd myEnd = association.getEnd(0);
      AssociationEnd yourEnd = association.getEnd(1);

      UmpleClass myClass = model.getUmpleClass(myEnd.getClassName());
      UmpleClass yourClass = model.getUmpleClass(yourEnd.getClassName());
      
      AssociationVariable myAs = new AssociationVariable(myEnd.getRoleName(),myEnd.getClassName(),myEnd.getModifier(),null,myEnd.getMultiplicity(),association.getIsLeftNavigable());
      AssociationVariable yourAs = new AssociationVariable(yourEnd.getRoleName(),yourEnd.getClassName(),yourEnd.getModifier(),null,yourEnd.getMultiplicity(),association.getIsRightNavigable());
      myAs.setRelatedAssociation(yourAs);
      
      if(!"".equals(myEnd.getPriority())) { myAs.setPriority(myEnd.getPriority()); }
      if(!"".equals(yourEnd.getPriority())) { yourAs.setPriority(yourEnd.getPriority()); }
      
      if (association.isImmutable())
      {
        boolean set = myAs.setImmutable();
        if (set) { yourAs.setImmutable(); }
        else
        {
          setFailedPosition(association.getTokenPosition(),17);
        }
      }
      
      boolean added = myClass.addAssociationVariable(yourAs);
      if (!added)
      {
        if (myClass.isImmutable()) { setFailedPosition(association.getTokenPosition(),17); }
        else { setFailedPosition(association.getTokenPosition(),13); }
        return;
      }
      myClass.addAssociation(association);

      added = yourClass.addAssociationVariable(myAs);
      if (!added)
      {
        if (myClass == yourClass) { setFailedPosition(association.getTokenPosition(),18); }
        else { setFailedPosition(association.getTokenPosition(),13); }
        return;
      }
      
      yourClass.addAssociation(association);

      if (myAs.getIsNavigable())
      {
        yourClass.addReferencedPackage(myClass.getPackageName());
      }

      if (yourAs.getIsNavigable())
      {
        myClass.addReferencedPackage(yourClass.getPackageName());
      }      

    }
  }

  private void checkSortedAssociations()
  {
    for (Association association : model.getAssociations()) 
    {
      if (associationIsBetweenClassAndInterface (association)){continue;}
      AssociationEnd myEnd = association.getEnd(0);
      AssociationEnd yourEnd = association.getEnd(1);

      UmpleClass myClass = model.getUmpleClass(myEnd.getClassName());
      UmpleClass yourClass = model.getUmpleClass(yourEnd.getClassName());

      String value;

      if(!"".equals(yourEnd.getPriority())){
        Attribute temp = yourClass.getAttribute(yourEnd.getPriority());
        if(temp != null)
        {
          if(Pattern.matches("Integer|Short|Long|Float|Double|String", temp.getType())) 
          {
            String attributeType = yourClass.getName();
            String priorityType = temp.getType();
            String sortedName = yourEnd.getPriority().substring(0,1).toUpperCase() + yourEnd.getPriority().substring(1);
            String php_codeblock = 
                "\n      function($x, $y)\n"+
                    "      {\n"+
                    "        return $x->get"+ sortedName +"() -\n"+ 
                    "               $y->get"+ sortedName +"();\n"+
                    "      }";  
            String java_codeblock = 
                "\n      new Comparator<" + attributeType +">(){\n"+
                    "        @Override\n"+
                    "        public int compare("+attributeType+" arg0, "+attributeType+" arg1)\n"+ 
                    "        {\n"+
                    "          return (("+priorityType+")arg0.get"+sortedName+"()).compareTo(\n"+
                    "                 (("+priorityType+")arg1.get"+sortedName+"()));\n"+ 
                    "        }\n"+
                    "      }";    

            Attribute priority = new Attribute(yourEnd.getRoleName()+"Priority","Comparator<" + attributeType +">", "", "", false, yourClass);
            CodeBlock cb = new CodeBlock();
            cb.setCode("Php", php_codeblock);
            cb.setCode("Java", java_codeblock);
            cb.setCode("Ruby", "\"\"");
            priority.setCodeblock(cb); 
            myClass.addAttribute(priority);

          } 
          else
            setFailedPosition(association.getTokenPosition(), 24, yourEnd.getPriority(), myClass.getName());
        }
        else
          setFailedPosition(association.getTokenPosition(), 25, yourClass.getName(), yourEnd.getPriority());
      }

      if(!"".equals(myEnd.getPriority())){
        Attribute temp = myClass.getAttribute(myEnd.getPriority());
        if(temp != null)
        {
          if(Pattern.matches("Integer|Short|Long|Float|Double|String", temp.getType()))
          {
            String attributeType = myClass.getName() ;
            String priorityType = temp.getType();
            String sortedName = myEnd.getPriority().substring(0,1).toUpperCase() + myEnd.getPriority().substring(1);
            String php_codeblock = 
                "\n      function($x, $y)\n"+
                    "      {\n"+
                    "        return $x->get"+ sortedName +"() -\n"+ 
                    "               $y->get"+ sortedName +"();\n"+
                    "      }";  
            String java_codeblock = 
                "\n      new Comparator<" + attributeType +">(){\n"+
                    "        @Override\n"+
                    "        public int compare("+attributeType+" arg0, "+attributeType +" arg1)\n"+ 
                    "        {\n"+
                    "          return (("+priorityType+")arg0.get"+sortedName+"()).compareTo(\n"+
                    "                 (("+priorityType+")arg1.get"+sortedName+"()));\n"+ 
                    "        }\n"+
                    "      }";    

            Attribute priority = new Attribute(myEnd.getRoleName()+"Priority","Comparator<" + attributeType +">", "", "", false, myClass);
            CodeBlock cb = new CodeBlock();
            cb.setCode("Php", php_codeblock);
            cb.setCode("Java", java_codeblock);
            cb.setCode("Ruby", "\"\"");
            priority.setCodeblock(cb); 
            yourClass.addAttribute(priority);
          }  
          else
            setFailedPosition(association.getTokenPosition(), 24, myEnd.getPriority(), yourClass.getName());
        }
        else
          setFailedPosition(association.getTokenPosition(), 25, myClass.getName(), myEnd.getPriority());
      }
    }
  }
  
   private void checkClassInterfaceAssocations(){
    for (Association a : model.getAssociations()){
      if (associationIsBetweenClassAndInterface(a)){
        boolean hasCorrectArrow = !a.getIsLeftNavigable()&&a.getIsRightNavigable(); // Assocation has "->" arrow
        if (!hasCorrectArrow){
          setFailedPosition(a.getTokenPosition(), 20, a.getEnd(0).getClassName());
        }
      }
    }
  }
  
  /*
   * Analyzes a token flagged to be a method in which case the data that makes up the method will be populated into a
   * method instance and added to an Umple element (which could be an Umple class).
   * 
   * @param method The token flagged to be the method.
   * @param uElement The Umple element for which the method will be added.
   */
  private void analyzeMethod(Token method, UmpleElement uElement)
  {
    String modifier = "";
    Method aMethod = new Method("","","",false);

  // Set method position
  aMethod.setPosition(method.getPosition());
  aMethod.setEndPosition(method.getEndPosition());
  
    // Add comments above the method to the method.
    for (Comment c : lastComments)
    {
      aMethod.addComment(c);
    }

    // Go through all the sub tokens of the "method token" to obtain details about it, using them to populate a method instance.
    List<String> langs = new ArrayList<String>();
    CodeBlock cb = new CodeBlock("");
    boolean canClear = false;
    for(Token token : method.getSubTokens())
    {
      if (token.is("modifier"))
      {
        modifier += " " + (token.getValue());
        aMethod.setModifier(modifier);
      }
      else if(token.is("static"))
      {
        modifier += " static ";
        aMethod.setModifier(modifier);
      }
      else if (token.is("type"))
      {
        aMethod.setType(token.getValue());
      }
      else if (token.is("list"))
      {
        aMethod.setType(aMethod.getType()+"[]");
      }
      else if (token.is("methodDeclarator"))
      {
        analyzeMethodDeclarator(token, aMethod);
      }
      else if (token.is("code"))
      {        
        if(langs.isEmpty())
        {
          cb.setCode(token.getValue());
        }  
        else
        {
          for(String str: langs)
          {
            cb.setCode(str,(cb.getCode(str)!=null?cb.getCode(str)+"\n":"")+token.getValue());
          }
        }
        canClear = true;
      }
      else if (token.is("codeLang"))
      {
        if(canClear)
        {
          langs.clear();
          canClear = false;
        }
        langs.add(token.getValue());
      }
      else if (token.is("precondition")){ 
        if (uElement instanceof UmpleClass){
          analyzePrecondition(token, (UmpleClass) uElement, aMethod);
        }  
      }
    }
    MethodBody meth = new MethodBody(cb);
    aMethod.setMethodBody(meth);

    // Add method to Class or Interface
    if (uElement instanceof UmpleClass)
    {
      UmpleClass uClass = (UmpleClass) uElement;
      
      boolean shouldAddMethod = verifyIfMethodIsConstructorOrGetSet(uClass, aMethod);
      if (!uClass.hasMethod(aMethod) && shouldAddMethod )
      {
        uClass.addMethod(aMethod); 
      }
    }
    else if (uElement instanceof UmpleInterface)
    {
      UmpleInterface uInterface = (UmpleInterface) uElement;
      if (!uInterface.hasMethod(aMethod))
      {
        uInterface.addMethod(aMethod); 
      }
    }  
  }

  /*
   * Analyzes a method header, from a token flagged to be one, to populate a method instance for things such as the
   * method name, type and parameters.
   * 
   * @param token The token flagged to be a method header.
   * @param aMethod The method to be populated from the analysis of the token.
   */
  private void analyzeMethodDeclarator(Token token, Method aMethod)
  {
    // Go through all sub tokens of the method token to obtain data such as the methods name, parameters etc and add them to the method.
    for(Token methodToken : token.getSubTokens())
    {
      if (methodToken.is("methodName"))
      {
        aMethod.setName(methodToken.getValue());
      }
      if (methodToken.is("parameterList"))
      {
        for(Token parameterToken : methodToken.getSubTokens())
        {
          boolean isList = false;
          if (parameterToken.is("parameter"))
          {
            String paramType="";
            if (parameterToken.getSubToken("type") != null)
            {
              paramType = parameterToken.getSubToken("type").getValue();
            }
            if (parameterToken.getSubToken("list") != null)
            {
              isList = parameterToken.getSubToken("list").getValue() != null;        
            }

            String paramName = parameterToken.getSubToken("name").getValue();
            MethodParameter aParameter  = new MethodParameter(paramName,paramType,null,null, false);
            aParameter.setIsList(isList);
            aMethod.addMethodParameter(aParameter);
          }
        }
      }
    }
  }

  /*
   * Analyzes a constant, from a token flagged to be one, to add a constant instance to an Umple element.
   * 
   * @param constantToken The token flagged to be a constant.
   * @param uElement The Umple element for which a new constant will be added (populated from analysis of the token).
   */
  private void analyzeConstant(Token constantToken, UmpleElement uElement)
  {
    Constant aConstant = new Constant("","","","");
    String modifier = "";

    // Create the Constant Object
    for(Token token : constantToken.getSubTokens())
    {
      if (token.is("modifier"))
      {
        modifier += " " + (token.getSubToken(0).getName());
        aConstant.setModifier(modifier);
      }
      else if (token.is("name"))
      {
        aConstant.setName(token.getValue());
      }
      else  if (token.is("type"))
      {
        aConstant.setType(token.getValue());
      }
      else  if (token.is("value"))
      {
        aConstant.setValue(token.getValue());
      }
    }  
    // Add constant to Class or Interface
    if (uElement instanceof UmpleClass)
    {
      UmpleClass uClass = (UmpleClass) uElement;
      uClass.addConstant(aConstant);
    }
    else if (uElement instanceof UmpleInterface)
    {
      UmpleInterface uInterface = (UmpleInterface) uElement;
      uInterface.addConstant(aConstant);
    }  
  }

  private void analyzeInjectionCode(Token injectToken, UmpleClass aClass)
  {
    String type = injectToken.is("beforeCode") ? "before" : "after";    
    CodeBlock cb = new CodeBlock();
    CodeInjection injection = new CodeInjection(type,injectToken.getValue("operationName"),"",aClass);
    makeCodeInject(injectToken,injection,cb,aClass);
    injection.setSnippet(cb);
    aClass.addCodeInjection(injection);    
  }
  
  private void makeCodeInject(Token injectToken,CodeInjection injection, CodeBlock cb, UmpleClass aClass)
  {
    List<String> langs = new ArrayList<String>();
    for(Token sub: injectToken.getSubTokens())
    {
      if(sub.is("codeLang"))
      {
        langs.add(sub.getValue());
      }
      if(sub.is("code"))
      {          
        if(langs.size()==0)
        {
          cb.setCode(sub.getValue());
        }
        else
        {
          for(String lang:langs)
            {
            cb.setCode(lang,sub.getValue());
            }
        }
        langs.clear();      
      }
      if(sub.is("codeInject"))
      {
        makeCodeInject(sub,injection, cb , aClass);
      }
    }    
    injection.setPosition(injectToken.getPosition());
    
  }

  private void analyzeKey(Token keyToken, UmpleClass aClass)
  {
    if (aClass.getKey().isProvided())
    {
      setFailedPosition(keyToken.getPosition(), 7, keyToken.getParentToken().getValue("name"));
    }

    if (keyToken.is("defaultKey"))
    {
      aClass.getKey().setIsDefault(true);
      return;
    }

    List<String> tokensAdded = new ArrayList<String>();
    Boolean tokenMatch;
    for(Token token : keyToken.getSubTokens())
    {
      tokenMatch = false;
      
      if (!token.is("keyId"))
      {
        continue;
      }
 
      //Checks for duplicate attributes/associations/stateMachines
      if(tokensAdded.contains(token.getValue()))
      {
        setFailedPosition(keyToken.getPosition(), 26, token.getValue(), keyToken.getParentToken().getValue("name"));
      }
   
    if(!aClass.hasAttributes() && !aClass.hasAssociations() && !aClass.hasStateMachines())
      {
        setFailedPosition(keyToken.getPosition(), 27, token.getValue(), keyToken.getParentToken().getValue("name"));
      }
      else{
        if(aClass.hasAttributes())
        {
          for(Attribute aAttribute : aClass.getAttributes())
          {
            if(aAttribute.getName().equals(token.getValue()))
            {
              tokenMatch = true;
            }
        }
        }
      
        if(aClass.hasAssociations())
      {
        AssociationEnd firstEnd, secondEnd;
        String firstEndName, secondEndName;
  
        for(Association aAssociation : aClass.getAssociations())
          { 
            firstEnd = aAssociation.getEnd(0);
            secondEnd = aAssociation.getEnd(1);
            firstEndName =  firstEnd.getRoleName();
            secondEndName = secondEnd.getRoleName();        
                      
            if(firstEndName.equals(token.getValue()) || secondEndName.equals(token.getValue()))
            {
              tokenMatch = true;
            }
        }
      }  

        if(aClass.hasStateMachines())  
        {
        for(StateMachine aStateMachine : aClass.getStateMachines())
        {
            if(aStateMachine.getName().equals(token.getValue()))
            {
              tokenMatch = true;
            }      
        }
      }      

      if(!tokenMatch)
      {
        setFailedPosition(keyToken.getPosition(), 27, token.getValue(), keyToken.getParentToken().getValue("name"));
      }
    }
      aClass.getKey().addMember(token.getValue());
      tokensAdded.add(token.getValue()); 
    }
  }

  private void analyzeSymmetricReflexiveAssociation(Token symmetricReflexiveAssociationToken, UmpleClass aClass)
  {
    String myName = symmetricReflexiveAssociationToken.getValue("roleName");
    String myType = aClass.getName();
    String myModifier = "symmetricreflexive";
    String myBound = symmetricReflexiveAssociationToken.getValue("bound");
    String myLowerBound = symmetricReflexiveAssociationToken.getValue("lowerBound");
    String myUpperBound = symmetricReflexiveAssociationToken.getValue("upperBound");
    Multiplicity myMult = new Multiplicity();
    myMult.setBound(myBound);
    myMult.setRange(myLowerBound,myUpperBound);

    AssociationVariable myAs = new AssociationVariable(myName,myType,myModifier,null,myMult,true);
    AssociationVariable yourAs = new AssociationVariable(myName,myType,myModifier,null,myMult,true);

    myAs.setRelatedAssociation(yourAs);
    aClass.addAssociationVariable(yourAs);
    
    AssociationEnd leftEnd = new AssociationEnd(null,myType,myModifier,myType,myMult);
    AssociationEnd rightEnd = new AssociationEnd(myName,myType,myModifier,myType,myMult);
    Association assoc = new Association(false, true, leftEnd, rightEnd);
    aClass.addAssociation(assoc);
  }

  private Association createAssociation(String navigation, AssociationEnd firstEnd, AssociationEnd secondEnd)
  {
    Association association;
    if(navigation != null){
      boolean isNavigable = "--".equals(navigation);
      boolean isFirstNavigable = "<-".equals(navigation) || isNavigable;
      boolean isSecondNavigable = "->".equals(navigation) || isNavigable;
      association = new Association(isFirstNavigable,isSecondNavigable,firstEnd,secondEnd);
    }else{
      association = new Association(true, true, firstEnd, secondEnd);
    }
    return association;
  }

  private Association analyzeAssociation(Token associationToken, String defaultMyType)
  {
    Token parentToken = associationToken.getParentToken();
    Token gParentToken = parentToken.getParentToken();
    boolean isAssociationClass = parentToken.is("associationClassDefinition") || gParentToken.is("associationClassDefinition");
    boolean isSingleAssociationEnd = associationToken.is("singleAssociationEnd");
    boolean isInlineAssociation = associationToken.is("inlineAssociation");
    Token myEndToken = null;
    Token yourEndToken = null;
    String navigation = null;
    String associationModifier = null;
    String name = null;

    //Issue 213/131: associations can be inside AssociationClasses, but
    //inline associations inside associationClasses are treated just like in regular classes
    if(isAssociationClass && !isInlineAssociation){
        if(isSingleAssociationEnd){
            myEndToken = parentToken;
            associationModifier = associationToken.getValue("modifier");
            navigation = null;
            name = parentToken.getValue("name");

        }else{ //association inside associationClass
            myEndToken = parentToken.getParentToken();
            associationModifier = parentToken.getValue("modifier");
            navigation = parentToken.getValue("arrow");
            name = gParentToken.getValue("name");
        }
        yourEndToken = associationToken;
    }else{//inline association or external "association{..}" block
        name = parentToken.getValue("name");
        int myMultOffset = 0;
        int yourMultOffset = 2;
        Token associationModifierToken = associationToken.getSubToken("modifier");
        if (associationModifierToken != null){
          associationModifier = associationModifierToken.getValue();
          myMultOffset++;
          yourMultOffset++;
        }
        myEndToken = associationToken.getSubToken(myMultOffset);
        navigation = associationToken.getValue("arrow");
        yourEndToken = associationToken.getSubToken(yourMultOffset);
    }
    AssociationEnd firstEnd = createPreliminaryAssociationEnd(myEndToken, defaultMyType);
    AssociationEnd secondEnd = createPreliminaryAssociationEnd(yourEndToken, defaultMyType);
    if(firstEnd == null || secondEnd == null){
        setFailedPosition(associationToken.getPosition(), 8, name);
        return null;
    }
    String myType = firstEnd.getClassName();
    String myRoleName = firstEnd.getRoleName();
    String yourType = secondEnd.getClassName();
    String yourRoleName = secondEnd.getRoleName();
    Multiplicity myMult = firstEnd.getMultiplicity();
    Multiplicity yourMult = secondEnd.getMultiplicity();

    if("".equals(firstEnd.getClassName())){
        firstEnd.setClassName(defaultMyType);
    }
    firstEnd.setReferenceToClassName(yourType);
    secondEnd.setReferenceToClassName(myType);
    //Association Classes have Mutiplicities switched between ends: an association
    //between A and B in associationClass C becomes A -- C and B -- C
    if(isAssociationClass){
        firstEnd.setMultiplicity(secondEnd.getMultiplicity());
        secondEnd.setMultiplicity(myMult);
    }
    updateAssociationEnds(firstEnd,secondEnd);

    // Trap cases where both ends are the same class (reflexive) and 
    // there is no or same role name and same multiplicity. Fixes issue 295
    if(myType.equals(yourType) && ((myRoleName == null && yourRoleName == null) || (myRoleName != null && yourRoleName != null && myRoleName.equals(yourRoleName))) && myMult.toString().equals(yourMult.toString())) {
      setFailedPosition(associationToken.getPosition(), 21, myType);
      return null;
    }
  
    if (firstEnd.getRoleName().equals(secondEnd.getRoleName()) && firstEnd.getClassName().equals(secondEnd.getClassName())){
      setFailedPosition(associationToken.getPosition(), 32, firstEnd.getRoleName());
      return null;
    }  
  
    Association association = createAssociation(navigation,firstEnd,secondEnd);
    
    if (associationModifier != null && "immutable".equals(associationModifier)){
      association.setImmutable();
    }
    association.setTokenPosition(associationToken.getPosition());
    association.setTokenEndPosition(associationToken.getEndPosition());
    
    if (!association.isValid()){
      Token atFaultToken = association.whoIsInvalid() == 0 ? myEndToken : yourEndToken;
      String invalidBound = atFaultToken.getValue("bound") == null ? invalidBound = atFaultToken.getValue("lowerBound") + ".." + atFaultToken.getValue("upperBound") : atFaultToken.getValue("bound");
      setFailedPosition(atFaultToken.getPosition(), 9, invalidBound);
      return null;
    }
    model.addAssociation(association);
    if(!isInlineAssociation){ unlinkedAssociations.add(association); }
    return association;
  }

  /*
   * Given a [[associationEnd]], [[singleAssociationEnd]], [[inlineAssociationEnd]] or 
   * a [[associationClassDefinition]] token, creates a
   * preliminary AssociationEnd object to help in the creation of an Association object.
   * The resulting object will have to be completed with setReferenceToClassName()depending on the type of association.
   * @return an AssociationEnd object or null if an error occured
  */
  private AssociationEnd createPreliminaryAssociationEnd(Token associationEndToken, String defaultType){
    if(associationEndToken != null){
      String name, type, modifier, roleName, bound, lowerBound, upperBound, priority;
      bound = lowerBound = upperBound = priority = roleName = null;
      Multiplicity mult = new Multiplicity();
      String typeIndex;

      //special case when [[singleAssociationEnd]] is used in one end: the 
      //parent associationClassDefinition is used as the other
      if(associationEndToken.is("associationClassDefinition")){
        name = null;
        type = associationEndToken.getValue("name");
        modifier = null;
        mult.setRange("1","1");
        typeIndex = "name";
      }else{
        type = associationEndToken.getValue("type");
        modifier = associationEndToken.getValue("modifier");
        roleName = associationEndToken.getValue("roleName");
        bound = associationEndToken.getValue("bound");
        lowerBound = associationEndToken.getValue("lowerBound");
        upperBound = associationEndToken.getValue("upperBound");
        priority = associationEndToken.getValue("priority");
        mult.setRange(lowerBound, upperBound);
        mult.setBound(bound);
        typeIndex = "type";
      }
      // Report an error if the multiplicity is invalid
      if (!mult.isValid()){
        String invalidBound = bound == null ? lowerBound + ".." + upperBound : bound;
        setFailedPosition(associationEndToken.getPosition(), 4, invalidBound);
        return null;
      }
      if(type == null){
          type = defaultType;
      }
      AssociationEnd assocEnd = new AssociationEnd(roleName,type,modifier,null,mult);

      if(priority != null){
          assocEnd.setPriority(priority);
      }
      positionToClassNameReference.put(associationEndToken.getPosition(typeIndex),type);
      return assocEnd;
    }else{
        return null;
    }
  }

  private void updateAssociationEnds(AssociationEnd firstEnd, AssociationEnd secondEnd)
  {

    if (firstEnd.getRoleName().length() == 0)
    { 
      String rawName = StringFormatter.toCamelCase(firstEnd.getClassName());
      String name = firstEnd.getMultiplicity().isMany() ? model.getGlossary().getPlural(rawName) : rawName;
      firstEnd.setRoleName(name);
      firstEnd.setIsDefaultRoleName(true);
    }

    if (secondEnd.getRoleName().length() == 0)
    {
      String rawName = StringFormatter.toCamelCase(secondEnd.getClassName());
      String name = secondEnd.getMultiplicity().isMany() ? model.getGlossary().getPlural(rawName) : rawName;
      secondEnd.setRoleName(name);
      secondEnd.setIsDefaultRoleName(true);
    }
  }

  /*
   * Analyzes a token flagged to be an association within an Umple class to create an instance of one and add it to the class.
   * 
   * @param inlineAssociationToken The token flagged to be an inline association.
   * @param aClass The Umple class for which an association instance will be added (populated from analysis of the token).
   */
  private void analyzeinlineAssociation(Token inlineAssociationToken, UmpleClass aClass)
  {
    Association association = analyzeAssociation(inlineAssociationToken,aClass.getName());

    if (!getParseResult().getWasSuccess())
    {
      return;
    }

    AssociationEnd myEnd = association.getEnd(0);
    AssociationEnd yourEnd = association.getEnd(1);

    AssociationVariable myAs = new AssociationVariable(myEnd.getRoleName(),myEnd.getClassName(),myEnd.getModifier(),null,myEnd.getMultiplicity(),association.getIsLeftNavigable());
    AssociationVariable yourAs = new AssociationVariable(yourEnd.getRoleName(),yourEnd.getClassName(),yourEnd.getModifier(),null,yourEnd.getMultiplicity(),association.getIsRightNavigable());
    myAs.setRelatedAssociation(yourAs);
    
    if(!"".equals(myEnd.getPriority())) { myAs.setPriority(myEnd.getPriority()); }
    if(!"".equals(yourEnd.getPriority())) { yourAs.setPriority(yourEnd.getPriority()); }
    
    if (association.isImmutable())
    {
      boolean set = myAs.setImmutable();
      if (set)
      {
        yourAs.setImmutable();
      }
      else
      {
        setFailedPosition(inlineAssociationToken.getPosition(),17);
      }
    }

    // Add comments above the association to the association.
    for (Comment c : lastComments)
    {
      yourAs.addComment(c);
    }

    boolean added = aClass.addAssociationVariable(yourAs);
    if (added)
    {
      unlinkedAssociationVariables.add(yourAs);
      aClass.addAssociation(association);
    }
    else
    {
      if (aClass.isImmutable()) { setFailedPosition(inlineAssociationToken.getPosition(),17); }
      
      else { setFailedPosition(inlineAssociationToken.getPosition(),13); }
    }
  }

  /*
   * Analyzes a token flagged to be an attribute within an Umple class to create an instance of one and add it to the class.
   * 
   * @param attributeToken The token flagged to be an attribute.
   * @param aClass The Umple class for which an attribute instance will be added (populated from analysis of the token).
   */
  private void analyzeAttribute(Token attributeToken, UmpleClass aClass)
  {
    boolean isAutounique = attributeToken.getValue("autounique") != null;
    boolean isUnique = attributeToken.getValue("unique") != null;
    boolean isLazy = attributeToken.getValue("lazy") != null;
    boolean validName = Token.isValidIdentifier(attributeToken.getValue("name"));
    boolean properName = !Token.isValidIdentifier(attributeToken.getValue("name"), "[A-Z]");
    boolean looksLikeAssociation = attributeToken.getValue("name").contains("--") || attributeToken.getValue("name").contains("->");
    looksLikeAssociation = looksLikeAssociation || attributeToken.getValue("name").contains("<-") || attributeToken.getValue("name").contains("..");
    looksLikeAssociation = looksLikeAssociation || attributeToken.getValue("name").contains("*");
    
    if(!validName)
    {
      if(looksLikeAssociation){
        setFailedPosition(attributeToken.getPosition(), 132, attributeToken.getValue("name"));
      } else {
        setFailedPosition(attributeToken.getPosition(), 130, attributeToken.getValue("name"));
      }
      
      return;
    }
    
    if(!properName && !"const".equals(attributeToken.getValue("modifier"))){
      setFailedPosition(attributeToken.getPosition(), 131, attributeToken.getValue("name"));
    }
    
    if (aClass.getIsSingleton() && !isLazy) 
    {
      isLazy = true;
      setFailedPosition(attributeToken.getPosition(), 1, attributeToken.getValue("name"));
    }

    String modifier = attributeToken.getValue("modifier");
    String type = attributeToken.getValue("type");
    String name = attributeToken.getValue("name");
    String value = attributeToken.getValue("value");
    String derivedValue = attributeToken.getValue("code");

  if (isLazyRedundant(isLazy, value))
  {
    setFailedPosition(attributeToken.getPosition(), 3, aClass.getName(), name);
  }

    for(Attribute aAttribute : aClass.getAttributes()){
      if (aAttribute.getName().equals(name)){
        setFailedPosition(attributeToken.getPosition(), 22, aClass.getName(), name);
      }
  }
    CodeBlock languageSpecificCode = new CodeBlock();
    if (derivedValue != null)
    {
      value = "";
      List<String> codelangs = new ArrayList<String>();
      for(Token tkn: attributeToken.getSubTokens())
      {
      if(tkn.is("codeLang"))
      {
        codelangs.add(tkn.getValue());
      } else if(tkn.is("code")) {
        if(codelangs.isEmpty())
        {
          languageSpecificCode.setCode(tkn.getValue());
        } else {
          for(String lang: codelangs)
          {
            languageSpecificCode.setCode(lang, tkn.getValue());
          }
          codelangs.clear();
        }
      }
      }
    }

    if ("defaulted".equals(modifier) && value == null)
    {
      setFailedPosition(attributeToken.getPosition(), 6, attributeToken.getValue("name"));
      return;
    }

    if (isUnique)
    {
      UniqueIdentifier uniqueIdentifier = new UniqueIdentifier(name,type,modifier,value);
      aClass.setUniqueIdentifier(uniqueIdentifier);
      return;
    }

    if (isAutounique)
    {
      type = "Integer";
    }

    if(type != null)
    { 
      Matcher m = Pattern.compile("([a-zA-Z_][0-9a-zA-Z_]*(<(.*)>)?)").matcher(type);
      if(!m.matches()) 
      {
        setFailedPosition(attributeToken.getPosition(), 140, type);
        return;
      }
    }
        
    if(type != null && value != null)
    {
      if(!compareTypeToValue(type,value))
      {
        setFailedPosition(attributeToken.getPosition(),141,type,value);  
      }
    }
    else if (type == null && value != null)
    {
      type = inferType(value, type);
    }
    else if(type == null)
    {
      type = "String";
    } 


    Attribute attribute = new Attribute(name,type,modifier,value,isAutounique,aClass);
    attribute.setIsLazy(isLazy);
    boolean isList = attributeToken.getValue("list") != null;

    if (name == null)
    {
      String rawName = StringFormatter.toCamelCase(type); 
      name = isList ? model.getGlossary().getPlural(rawName) : rawName;
    }

    if (derivedValue != null)
    {
      attribute.setPosition(attributeToken.getPosition());
      attribute.setEndPosition(attributeToken.getEndPosition());
      attribute.setIsDerived(true);
      attribute.setCodeblock(languageSpecificCode);
    }

    attribute.setIsList(isList);

    // Add comments above the attribute to the attribute.
    for (Comment c : lastComments)
    {
      attribute.addComment(c);
    }
  }
  

  private String inferType(String value, String type)
  {
    String inferredType;
    if(value.matches("-?[0-9]+\\.[0-9]+"))
    {
      inferredType = "Double";
    }
    else if(value.matches("-?[0-9]+"))
    {
      inferredType = "Integer";
    }
    else if(value.matches("(true|false)"))
    {
      inferredType = "Boolean";
    }
    else if(value.matches("\"\\d{4}-[0-3][0-9]-[0-1][0-9]\""))
    {
      inferredType="Date";
    }
    else if(value.matches("\"[0-2][0-9]:[0-5][0-9]:[0-5][0-9]\""))
    {
      inferredType = "Time";
    }
    else if(value.matches("\".*\""))
    {
      inferredType = "String";
    }
    else if(type == null)
    {
      Matcher m = Pattern.compile("(new\\s+)([a-zA-Z_][0-9a-zA-Z_]*(\\s*<(.*)>)?)(\\s*\\(\\))").matcher(value);
      if(m.matches())        
      {  
        inferredType = m.group(2);
      }
      else
      { 
        //if anything other than the default new X(); one cannot infer the type
        inferredType = "String";
      }
    }
    //worst case scenarios when we can't anticipate what a user will use as value.
    //and to ignore inheritance/polymorphism 
    else if(type != null)
    {
      inferredType = type; 
    }
    else
    {
      inferredType = "String";
    }
    return inferredType;
  }
  
  private boolean compareTypeToValue(String type, String value)
  { 
    //cannot infer null
    if(value.equals("null"))
    {
      return true;
    }

    String inferredType = inferType(value,type);   
    if(type.equals("String") && (inferredType.equals("Date") || inferredType.equals("Time")))
    {
      return true;
    }
    else if((type.equals("Float") || type.equals("float") || type.equals("Double") || type.equals("double")) && (inferredType.equals("Integer") || inferredType.equals("Double")))
    {
      return true;
    }
    else if(type.equals("int") && inferredType.equals("Integer"))
    {
      return true;
    }
    else if(type.equals("boolean") && inferredType.equals("Boolean"))
    {
      return true;
    } 
    else
    {
      return inferredType.equals(type);
    }
  } 

  private Boolean isLazyRedundant(Boolean isLazy, String value)
  {
    return (isLazy && value != null);
  }
  
  private void analyzeException(Token exception, UmpleClass aClass)
  {
    Token sub = exception.getSubToken(0);
    if(sub.is("misnamedAttribute"))
    {
      if(!Token.isValidIdentifier(sub.getValue("name")))
      {
        boolean looksLikeAssociation = sub.getValue("name").contains("--") || sub.getValue("name").contains("->");
        looksLikeAssociation = looksLikeAssociation || sub.getValue("name").contains("<-") || sub.getValue("name").contains("..");
        looksLikeAssociation = looksLikeAssociation || sub.getValue("name").contains("*");
        if(looksLikeAssociation)
        {
          setFailedPosition(sub.getPosition(), 132, sub.getValue("name"));
        }
        else 
        {
          setFailedPosition(sub.getPosition(), 130, sub.getValue("name"));
        }
        return;
      }
      if(Token.isValidIdentifier(sub.getValue("name"), "[A-Z]"))
      {
        setFailedPosition(sub.getPosition(), 131, sub.getValue("name"));
      }
      String type = sub.getValue("type");
      if(type!=null)
      {
        if(!Pattern.matches("([a-z]|[A-Z]|_)(\\d|\\w|<|>|,)*", type))
        {
          setFailedPosition(sub.getPosition(), 140, type);
        }
      }
    }
    else if(sub.is("malformedStatement1")||sub.is("malformedStatement2"))
    {
      String extraCode = "";
      for(Token token:sub.getSubTokens())
      {
        if(token.is("stuff"))
        {
          extraCode += token.getValue()+ " ";
        }
      }
      if(sub.getValue("innerstuff")!=null)
      {
        extraCode += "{"+sub.getValue("innerstuff")+"};";
      }
      else
      {
        extraCode += ";";
      }
      aClass.appendExtraCode("// line " + exception.getPosition().getLineNumber() + " " + exception.getPosition().getRelativePath(aClass, "Java"));
      aClass.appendExtraCode("  "+extraCode+"\n");
      setFailedPosition(sub.getPosition(), 1007, sub.getValue("name"));
    }
    else if(sub.is("malformedStatemachine1")||sub.is("malformedStatemachine2"))
    {
      String extraCode = "";
      for(Token token:sub.getSubTokens())
      {
        if(token.is("stuff"))
        {
          extraCode += token.getValue()+ " ";
        }
      }
      extraCode += "\n  {\n    "+sub.getValue("innerstuff")+"\n  }";
      aClass.appendExtraCode("// line " + exception.getPosition().getLineNumber() + " " + exception.getPosition().getRelativePath(aClass, "Java"));
      aClass.appendExtraCode("  "+extraCode+"\n");
      setFailedPosition(sub.getPosition(), 1006, sub.getValue("name"));
    }
    else if(sub.is("malformedMethod"))
    {
      String extraCode = "";
      for(Token token:sub.getSubTokens())
      {
        if(token.is("stuff"))
        {
          extraCode += token.getValue()+ " ";
        }
      }
      extraCode += "\n  {\n    "+sub.getValue("innerstuff")+"\n  }";
      aClass.appendExtraCode("// line " + exception.getPosition().getLineNumber() + " " + exception.getPosition().getRelativePath(aClass, "Java"));
      aClass.appendExtraCode("  "+extraCode+"\n");
      setFailedPosition(sub.getPosition(), 1008, sub.getValue("name"));
    }
  }
} 
