/*
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.royale.compiler.internal.parsing.mxml;

import static org.apache.royale.compiler.mxml.IMXMLLanguageConstants.ATTRIBUTE_NAME;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.royale.compiler.common.IFileSpecificationGetter;
import org.apache.royale.compiler.common.Multiname;
import org.apache.royale.compiler.config.CompilerDiagnosticsConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.metadata.IMetaTag;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.royale.compiler.definitions.references.ReferenceFactory;
import org.apache.royale.compiler.filespecs.IFileSpecification;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.PackageDefinition;
import org.apache.royale.compiler.internal.definitions.VariableDefinition;
import org.apache.royale.compiler.internal.mxml.MXMLDialect;
import org.apache.royale.compiler.internal.parsing.as.ASParser;
import org.apache.royale.compiler.internal.parsing.as.IncludeHandler;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.scopes.MXMLFileScope;
import org.apache.royale.compiler.internal.scopes.PackageScope;
import org.apache.royale.compiler.internal.scopes.TypeScope;
import org.apache.royale.compiler.internal.semantics.PostProcessStep;
import org.apache.royale.compiler.internal.tree.as.ScopedBlockNode;
import org.apache.royale.compiler.internal.tree.as.metadata.MetaTagsNode;
import org.apache.royale.compiler.internal.tree.mxml.MXMLNodeBase;
import org.apache.royale.compiler.internal.units.MXMLCompilationUnit;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.mxml.IMXMLData;
import org.apache.royale.compiler.mxml.IMXMLLanguageConstants;
import org.apache.royale.compiler.mxml.IMXMLNamespaceAttributeData;
import org.apache.royale.compiler.mxml.IMXMLTagAttributeData;
import org.apache.royale.compiler.mxml.IMXMLTagData;
import org.apache.royale.compiler.mxml.IMXMLTextData;
import org.apache.royale.compiler.mxml.IMXMLTextData.TextType;
import org.apache.royale.compiler.mxml.IMXMLUnitData;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.MXMLLibraryTagNotTheFirstChildProblem;
import org.apache.royale.compiler.problems.MXMLUnresolvedTagProblem;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;

/**
 * This class analyzes the tags and attributes of an MXML file, as represented
 * in an MXMLData object, and builds an ASFileScope which is the
 * externally-visible API of the MXML file.
 * <p>
 * This class is conceptually similar to ABCScopeBuilder, which builds one or
 * more ASFileScopes from an ABC block.
 */
public class MXMLScopeBuilder
{
    // RegEx for splitting implements="a.b.c , d.e.f"
    private static final String IMPLEMENTS_SPLITTER = ",";

    public MXMLScopeBuilder(MXMLCompilationUnit compilationUnit, IFileSpecificationGetter fileSpecGetter, IMXMLData mxmlData, String qname, String fileName)
    {
        this.project = compilationUnit.getProject();

        this.compilationUnit = compilationUnit;

        this.mxmlData = mxmlData;
        
        this.qname = qname;

        fileScope = new MXMLFileScope(compilationUnit, fileName, mxmlData);
        String packageName = Multiname.getPackageNameForQName(qname);
        packageScope = new PackageScope(fileScope, packageName);
        packageScope.setContainingScope(fileScope);
        PackageDefinition packageDefinition = new PackageDefinition(packageName);
        // CMP-742: packages created for MXML scopes should be marked implicit
        packageDefinition.setImplicit();
        packageDefinition.setContainedScope(packageScope);
        fileScope.addDefinition(packageDefinition);
        
        problems = new LinkedList<ICompilerProblem>();
        problems.addAll(mxmlData.getProblems());

        includeHandler = new IncludeHandler(fileSpecGetter);
        includeHandler.setProjectAndCompilationUnit(project, compilationUnit);

        this.fileSpecGetter = fileSpecGetter;
        assert fileSpecGetter != null;
    }

    // The MXML for which we're building a file scope.
    private IMXMLData mxmlData;

    // Each project knows how to resolve MXML tags to ActionScript classes.
    // Different projects can resolve the same tag to different classes.
    private RoyaleProject project;
    
    // The MXMLCompilationUnit for which we're building a file scope.
    private MXMLCompilationUnit compilationUnit;

    // The fully-qualified name of the MXML class.
    private String qname;

    // The file scope that we're building.
    private MXMLFileScope fileScope;
    
    // The package scope inside the file scope that contains the main class definition.
    private PackageScope packageScope;

    // Initially, this is the class definition for the main MXML class, inside the package scope.
    // As we process an <fx:Component> or <fx:Definition> tag, this changes to be the class
    // definition for the component or definition class.
    private ClassDefinition currentClassDefinition;

    // Initially, this is the class scope for the main MXML class, inside the package scope.
    // As we process an <fx:Component> or <fx:Definition> tag, this changes to be the class
    // scope for the component or definition class.
    private TypeScope currentClassScope;

    // Problems encountered while building the file scope.
    private Collection<ICompilerProblem> problems;
        
    private final IncludeHandler includeHandler;
    
    private final IFileSpecificationGetter fileSpecGetter;
    
    /**
     * Builds the file scope for the MXML file.
     * 
     * @return An {@code ASFileScope} representing the externally-visible
     * API of the MXML file.
     */
    public MXMLFileScope build()
    {
        includeHandler.enterFile(mxmlData.getPath());
        
        IMXMLTagData rootTag = mxmlData.getRootTag();
        if (rootTag == null)
            return fileScope;

        processRootTag(rootTag);

        ImmutableSet<String> includedFiles = includeHandler.getIncludedFiles();
        for (String includedFile : includedFiles)
            fileScope.addSourceDependency(includedFile);

        includeHandler.leaveFile();

        return fileScope;
    }

    private void processRootTag(IMXMLTagData rootTag)
    {
        IReference baseClass = fileScope.resolveTagToReference(rootTag);

        String implementsAttrValue = rootTag.getRawAttributeValue("implements");
        IReference[] implementedInterfaces = null;
        if (implementsAttrValue != null) //TODO this should use a parser method that collects qnames or identifiers
        {
            String interfaces[] = rootTag.getMXMLDialect().trim(implementsAttrValue).split(IMPLEMENTS_SPLITTER);
            implementedInterfaces = new IReference[interfaces.length];
            for( int i = 0; i < interfaces.length; ++i )
            {
                implementedInterfaces[i] = ReferenceFactory.packageQualifiedReference(project.getWorkspace(), interfaces[i].trim());
            }
        }

        Multiname mname = Multiname.crackDottedQName(this.project, qname);
        INamespaceDefinition packageNS = Iterables.getOnlyElement(mname.getNamespaceSet());
        assert packageNS instanceof INamespaceReference;
        currentClassDefinition = new ClassDefinition(mname.getBaseName(), (INamespaceReference)packageNS);
        currentClassDefinition.setLocation(rootTag);
        currentClassDefinition.setBaseClassReference(baseClass);
        currentClassDefinition.setImplementedInterfaceReferences(implementedInterfaces);
        currentClassDefinition.setMetaTags(new IMetaTag[0]);
        // Grr... CM clients expect the name start of the root class definition to be at 0.
        currentClassDefinition.setNameLocation(0, 0);
        packageScope.addDefinition(currentClassDefinition);
        fileScope.setMainClassDefinition(currentClassDefinition);

        currentClassScope = new TypeScope(packageScope, currentClassDefinition);
        currentClassScope.setContainingDefinition(currentClassDefinition);

        if (baseClass instanceof IResolvedQualifiersReference)
        {
            IDefinition baseDef = ((IResolvedQualifiersReference)baseClass).resolve(project);
            if (baseDef == null)
                problems.add(new MXMLUnresolvedTagProblem(rootTag));
            else
                currentClassScope.addImport(baseDef.getQualifiedName());           
        }

        currentClassDefinition.setContainedScope(currentClassScope);
        currentClassDefinition.setupThisAndSuper();
        
        // An <fx:Library> tag can only be the first child tag of the root tag.
        IMXMLTagData child = rootTag.getFirstChild(true);
        if (child != null && fileScope.isLibraryTag(child))
        {
            processLibraryTag(child);
            child = child.getNextSibling(true);    
        }
        
        // Process the subsequent child tags after the first.
        for (;
             child != null;
             child = child.getNextSibling(true))
        {
            processTag(child);
        }

        currentClassDefinition.buildContingentDefinitions();
    }

    /**
     * Builds {@link ClassDefinition}'s for &lt;fx:Definition&gt; tags
     * found in the specified &lt:fx:Library&gt; tag.
     * @param libraryTagData
     */
    private void processLibraryTag(IMXMLTagData libraryTag)
    {
        assert fileScope.isLibraryTag(libraryTag);
        
        for (IMXMLTagData child = libraryTag.getFirstChild(true);
             child != null;
             child = child.getNextSibling(true))
        {
            if (fileScope.isDefinitionTag(child))
                processDefinitionTag(child);
            
            // TODO create problems when library has children that are not
            // definition tags.
        }       
    }
    
    /**
     * Builds a {@link ClassDefinition} for the specified &lt;fx:Definition&gt; tag.
     * @param definitionTag
     */
    private void processDefinitionTag(IMXMLTagData definitionTag)
    {
        assert fileScope.isDefinitionTag(definitionTag);
        
        // An <fx:Definition> tag will create a new class.
        // Save off the current class definition and scope
        // because we're going to change them before recursing down.
        ClassDefinition oldClassDefinition = currentClassDefinition;
        TypeScope oldClassScope = currentClassScope;
        
        // Walk the attributes looking for 'name'.
        IMXMLTagAttributeData[] definitionTagAttrs = definitionTag.getAttributeDatas();
        String definitionName = null;
        int nameStart = -1;
        int nameEnd = -1;
        for (IMXMLTagAttributeData attr : definitionTagAttrs)
        {
            if ((!(attr instanceof IMXMLNamespaceAttributeData)) && (attr.hasValue()))
            {
               if ((attr.getURI() == null) && (ATTRIBUTE_NAME.equals(attr.getName())))
               {
                   if (definitionName == null)
                   {
                       definitionName = attr.getRawValue();
                       nameStart = attr.getValueStart() + 1;
                       nameEnd = attr.getValueEnd() - 1;
                   }
                   // TODO create problem if definition name has already been set.
               }
            }
        }
        
        // We expect one child tag inside <fx:Definition>.
        IMXMLTagData firstChild = definitionTag.getFirstChild(true);
        if (firstChild != null)
        {
            // TODO create problem if there is more than one child tag
            // in a definition.
            IMXMLTagData secondChild = firstChild.getNextSibling(true);
            if (secondChild != null)
                return;
            
            // This child tag specifies the base class of the definition class.
            String baseClassQName = fileScope.resolveTagToQualifiedName(firstChild);
            // TODO create problem if we can't resolve the base class name.
            if (baseClassQName == null)
                return;
            
            // Add a class definition for the definition class to the file scope.
            // The file scope will handle resolving a tag like <fx:MyDefinition>
            // to the class for <fx:Definition name="MyDefinition">.
            ClassDefinition fxDefinitionClassDefinition =
                fileScope.addFXDefinition(qname, definitionTag, definitionName, baseClassQName);
            fxDefinitionClassDefinition.setLocation(firstChild);
            fxDefinitionClassDefinition.setNameLocation(nameStart, nameEnd);
            
            // Set up the "context" for building definitions inside the definition class.
            currentClassDefinition = fxDefinitionClassDefinition;
            currentClassScope = (TypeScope)fxDefinitionClassDefinition.getContainedScope();
            
            // add the definitions inside the definition tag to the definition class scope.
            processTag(firstChild);
        }
        
        // Restore the previous class definition and scope.
        currentClassDefinition = oldClassDefinition;
        currentClassScope = oldClassScope;
    }
    
    private void processTag(IMXMLTagData tag)
    {
        includeHandler.onNextMXMLUnitData((IMXMLUnitData)tag);
        
        boolean recurse = true;
                
        if (fileScope.isScriptTag(tag))
        {
            processScriptTag(tag);
        }
        else if (fileScope.isStyleTag(tag))
        {
            processStyleTag(tag);
        }
        else if (fileScope.isMetadataTag(tag))
        {
            processMetadataTag(tag);
        }
        else if (fileScope.isComponentTag(tag))
        {
            processComponentTag(tag);
            // we've already processed the tags
            recurse = false;
        }
        else if (fileScope.isLibraryTag(tag))
        {
            // log a problem but continue processing the tag
            // so that a valid tree is formed
            MXMLLibraryTagNotTheFirstChildProblem problem = new MXMLLibraryTagNotTheFirstChildProblem(tag);
            problems.add(problem);
            processLibraryTag(tag);
            recurse = false;
        }
        else if (fileScope.isXMLTag(tag))
        {
            processXMLTag(tag);
            
            // Skip over this tag. Nothing inside it contributes to the externally-visible API.
            recurse = false;
        }
        else if (fileScope.isPrivateTag(tag) ||
                 fileScope.isXMLListTag(tag))
        {
           // Skip over these tags. Nothing inside them contributes to the externally-visible API.
           recurse = false;
        }
        else if (fileScope.isModelTag(tag))
        {
            processModelTag(tag);
            
            // Skip over this tag. Nothing inside it contributes to the externally-visible API.
            recurse = false;
        }
        else if (fileScope.isStringTag(tag))
        {
            processStringTag(tag);
        }
        
        // If the tag can be resolved to a qualified name,
        // import that name into the MXML class being defined.
        // This is how the old InterfaceCompiler worked,
        // but not how the old ImplementationCompiler worked.
        // There may be pathological cases in which the tag
        // actually turns out to be a property/style/event tag
        // once the MXML AST has been built, but in these cases
        // the spurious import will simply cause an ambiguous
        // identifier problem in Royale whereas the old compiler
        // would have compiled the code without error.
        String qname = fileScope.resolveTagToQualifiedName(tag);
        if (qname != null)
        {
            currentClassScope.addImport(qname);
            
            // CodeModel needs nodes for such implicit imports.
            // Keep track of the the implicit imports
            // on the ClassDefinition we're creating,
            // where MXMLClassDefinitionNode can later create nodes for them.
            currentClassDefinition.addImplicitImport(qname);

            // if the tag couldn't be resolved to a qname, assume it it's not
            // a state, as we can't check it it resolved to mx.states.State.
            if (IMXMLLanguageConstants.STATE.equals(tag.getShortName()))
                processState(tag, qname);
        }

        IMXMLTagAttributeData idAttribute = tag.getTagAttributeData("id");
        String id = tag.getRawAttributeValue("id");
        if (id != null)
            processID(tag, idAttribute);
        else
        {
            idAttribute = tag.getTagAttributeData("localId");
            id = tag.getRawAttributeValue("localId");
            if (id != null)
                processID(tag, idAttribute);        	
        }

        if (recurse)
        {
            for (IMXMLTagData child = tag.getFirstChild(true);
                 child != null;
                 child = child.getNextSibling(true))
            {
                processTag(child);
            }
        }
    }

    /**
     * Build scopes and definitions from a script tag.
     * 
     * @param scriptTag script tag
     * @see {@link MXMLScriptNode} for AST building.
     */
    private void processScriptTag(IMXMLTagData scriptTag)
    {
        assert fileScope.isScriptTag(scriptTag);
        
        IMXMLTagAttributeData sourceAttribute = scriptTag.getTagAttributeData(IMXMLLanguageConstants.ATTRIBUTE_SOURCE);
        if (sourceAttribute != null)
        {
            // script tag with source attribute
            String sourcePath = MXMLNodeBase.resolveSourceAttributePath(null, sourceAttribute, null);
            Reader sourceFileReader = null;
            try
            {
                Workspace workspace = project.getWorkspace();
                
                IFileSpecification sourceFileSpec = fileSpecGetter.getFileSpecification(sourcePath);
                sourceFileReader = sourceFileSpec.createReader();
                //TODO what do we do about errors that are encountered in the attached file?
                //is this even the correct approach?
                String scriptText = IOUtils.toString(sourceFileReader);
                
                // no need to pass in start offset because include handler will take care of it
                ASParser.parseFragment2(
                        scriptText,
                        sourcePath,
                        0,  
                        0,
                        0,
                        problems,
                        workspace,
                        null,
                        currentClassScope,
                        project.getProjectConfigVariables(),
                        EnumSet.of(PostProcessStep.CALCULATE_OFFSETS, PostProcessStep.POPULATE_SCOPE),
                        true,
                        includeHandler);

                fileScope.addSourceDependency(sourcePath);
            }
            catch (FileNotFoundException e)
            {
                // The error reporting will be handled during tree-building time, but the incremental
                // flow needs to have a dependency on the compilation unit during scope building time,
                // as builder may not necessarily ask for the syntax tree and the dependency get's
                // missed. CMP-2040
                project.addUnfoundReferencedSourceFileDependency(sourcePath, compilationUnit);
            }
            catch (IOException e)
            {
                // File-not-found will be reported by MXMLScriptNode during tree-building.
            }
            finally
            {
                if (sourceFileReader != null)
                    try { sourceFileReader.close(); }
                    catch (IOException e) { }
            }
        }
        else
        {
            final List<ScopedBlockNode> nodes = new ArrayList<ScopedBlockNode>(2);
            for (IMXMLUnitData unit = scriptTag.getFirstChildUnit(); unit != null; unit = unit.getNextSiblingUnit())
            {
                if (unit instanceof IMXMLTextData)
                {
                    final IMXMLTextData mxmlTextData = (IMXMLTextData)unit;
                    if (mxmlTextData.getTextType() != TextType.WHITESPACE)
                    {
                        final EnumSet<PostProcessStep> postProcess = EnumSet.of(
                                PostProcessStep.CALCULATE_OFFSETS, 
                                PostProcessStep.POPULATE_SCOPE);
                        final ScopedBlockNode node = ASParser.parseInlineScript(
                                null,
                                mxmlTextData,
                                problems,
                                currentClassScope,
                                project.getProjectConfigVariables(),
                                includeHandler,
                                postProcess);
                        assert node != null : "Expected node from ASParser.getScopesFromInlineScript().";
                        nodes.add(node);
                    }
                }
            }
        }
    }
    
    private void processStyleTag(IMXMLTagData styleTag)
    {
        assert fileScope.isStyleTag(styleTag);
        
        processSourceAttribute(styleTag);
    }
    
    private void processMetadataTag(IMXMLTagData metadataTag)
    {
        assert fileScope.isMetadataTag(metadataTag);
        
        for (IMXMLUnitData unit = metadataTag.getFirstChildUnit(); unit != null; unit = unit.getNextSiblingUnit())
        {
            if (unit instanceof IMXMLTextData)
            {
                final IMXMLTextData mxmlTextData = (IMXMLTextData)unit;
                if (mxmlTextData.getTextType() != TextType.WHITESPACE)
                {
                    MetaTagsNode metaTagNodes = ASParser.parseMetadata(project.getWorkspace(), mxmlTextData.getCompilableText(),
                            mxmlTextData.getSourcePath(),
                            mxmlTextData.getCompilableTextStart(),
                            mxmlTextData.getCompilableTextLine(),
                            mxmlTextData.getCompilableTextColumn(), problems);
                    if (metaTagNodes != null)
                    {
                    	if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
                    		System.out.println("MXMLScopeBuilder waiting for lock in processMetadataTag");
                        IFileSpecification containingFileSpec = fileScope.getWorkspace().getFileSpecification(fileScope.getContainingPath());
                    	if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
                    		System.out.println("MXMLScopeBuilder done with lock in processMetadataTag");
                        IMetaTag[] newMetaTags = metaTagNodes.buildMetaTags(containingFileSpec, currentClassDefinition);
                        if( newMetaTags != null )
                        {
                            IMetaTag[] oldMetaTags = currentClassDefinition.getAllMetaTags();

                            IMetaTag[] mergedTags = newMetaTags;
                            if( oldMetaTags != null )
                            {
                                // Merge the new metadata and the already existent metadata - there can be multiple
                                // <fx:Metadata> tags in the source
                                mergedTags = new IMetaTag [oldMetaTags.length + newMetaTags.length];
                                System.arraycopy(oldMetaTags, 0, mergedTags, 0, oldMetaTags.length);
                                System.arraycopy(newMetaTags, 0, mergedTags, oldMetaTags.length, newMetaTags.length);
                            }
                            currentClassDefinition.setMetaTags(mergedTags);
                        }
                        else
                        {
                            // nothing to do, no additional metadata was specified
                        }
                    }
                }
            }
        }
    }

    private void processComponentTag(IMXMLTagData componentTag)
    {
        assert fileScope.isComponentTag(componentTag);
        
        // An <fx:Definition> tag will create a new class.
        // Save off the current class definition and scope
        // because we're going to change them before recursing down.
        ClassDefinition oldClassDefinition = currentClassDefinition;
        TypeScope oldClassScope = currentClassScope;
        
        // Walk the attributes looking for 'className'.
        IMXMLTagAttributeData[] definitionTagAttrs = componentTag.getAttributeDatas();
        String className = null;
        int nameStart = -1;
        int nameEnd = -1;
        for (IMXMLTagAttributeData attr : definitionTagAttrs)
        {
            if ((!(attr instanceof IMXMLNamespaceAttributeData)) && (attr.hasValue()))
            {
               if ((attr.getURI() == null) && (attr.getName().equals("className")))
               {
                   if (className == null)
                   {
                       className = attr.getRawValue();
                       nameStart = attr.getValueStart() + 1;
                       nameEnd = attr.getValueEnd() - 1;
                   }
                   // TODO create problem if className has already been set.
               }
            }
        }
                
        // We expect one child tag inside <fx:Component>.
        IMXMLTagData firstChild = componentTag.getFirstChild(true);
        if (firstChild != null)
        {
            // TODO create problem if there is more than one child tag
            // in a definition.
            IMXMLTagData secondChild = firstChild.getNextSibling(true);
            if (secondChild != null)
                return;
            
            // This child tag specifies the base class of the component class.
            String baseClassQName = fileScope.resolveTagToQualifiedName(firstChild);
            // TODO create problem if we can't resolve the base class name.
            if (baseClassQName == null)
                return;
            
            // Add a class definition for the component class to the file scope.
            ClassDefinition fxComponentClassDefinition =
                fileScope.addFXComponent(qname, componentTag.getAbsoluteStart(), className, baseClassQName);
            fxComponentClassDefinition.setNameLocation(nameStart, nameEnd);
            
            if (className == null)
                fxComponentClassDefinition.setExcludedClass();
            
            // Set up the "context" for building definitions inside the definition class.
            currentClassDefinition = fxComponentClassDefinition;
            currentClassScope = (TypeScope)fxComponentClassDefinition.getContainedScope();

            currentClassDefinition.buildOuterDocumentMember(ReferenceFactory.resolvedReference(oldClassDefinition));

            // add the definitions inside the component tag to the component class scope.
            processTag(firstChild);
        } 
        
        // Restore the previous class definition and scope.
        currentClassDefinition = oldClassDefinition;
        currentClassScope = oldClassScope;
    }

    private void processState(IMXMLTagData tag, String qname)
    {
        if (!qname.equals(project.getStateClass()) || tag.getMXMLDialect() == MXMLDialect.MXML_2006)
            return;

        // if there is no name attribute, ignore it as a state, as name is
        // a required attribute
        IMXMLTagAttributeData nameAttribute = tag.getTagAttributeData(IMXMLLanguageConstants.ATTRIBUTE_NAME);
        if (nameAttribute == null)
            return;

        // TODO: need to handle evaluations of entities
        String stateName = nameAttribute.getRawValue();
        currentClassDefinition.addStateName(stateName);
    }

    private void processXMLTag(IMXMLTagData xmlTag)
    {
        assert fileScope.isXMLTag(xmlTag);
        
        processSourceAttribute(xmlTag);
    }
    
    private void processModelTag(IMXMLTagData modelTag)
    {
        assert fileScope.isModelTag(modelTag);
        
        processSourceAttribute(modelTag);
    }
    
    private void processStringTag(IMXMLTagData stringTag)
    {
        assert fileScope.isStringTag(stringTag);
        
        processSourceAttribute(stringTag);
    }
    
    /**
     * If the tag has a <code>source</code> attribute,
     * determine whether the source file exists or not.
     * If so, call <code>addSourceDependency()</code>
     * on the <code>MXMLFileScope</code>.
     * If not, call <code>addUnfoundReferenceSourceFileDependency()</code>
     * on the <code>RoyaleProject</code>.
     */
    private void processSourceAttribute(IMXMLTagData tag)
    {
        IMXMLTagAttributeData sourceAttribute = tag.getTagAttributeData(IMXMLLanguageConstants.ATTRIBUTE_SOURCE);
        if (sourceAttribute != null)
        {
            String sourcePath = MXMLNodeBase.resolveSourceAttributePath(null, sourceAttribute, null);
            Reader sourceFileReader = null;
            try
            {
                IFileSpecification sourceFileSpec = fileSpecGetter.getFileSpecification(sourcePath);
                sourceFileReader = sourceFileSpec.createReader();

                fileScope.addSourceDependency(sourcePath);
            }
            catch (FileNotFoundException e)
            {
                // The error reporting will be handled during tree-building time, but the incremental
                // flow needs to have a dependency on the compilation unit during scope building time,
                // as builder may not necessarily ask for the syntax tree and the dependency get's
                // missed. CMP-2040
                project.addUnfoundReferencedSourceFileDependency(sourcePath, compilationUnit);
            }
            finally
            {
                if (sourceFileReader != null)
                {
                    try
                    {
                        sourceFileReader.close();
                    }
                    catch (IOException e)
                    {
                    }
                }
            }
        }
    }

    private void processID(IMXMLTagData tag, IMXMLTagAttributeData idAttribute)
    {
        String id = idAttribute.getRawValue();
        IReference typeRef = fileScope.resolveTagToReference(tag);
        
        // TODO: put in the code below so that instances of the model tag
        // would have a resolvable class (ObjectProxy). We might want to do this a different way.
        // TODO: Also, we need some treatment of the component tag. That case is different,
        // however, in that is already has a class - we just need to add ClassFactory as a base class
        // (I think).
        if (typeRef == null)
        {
            if (fileScope.isModelTag(tag))
            {
                typeRef = ReferenceFactory.packageQualifiedReference(project.getWorkspace(), "mx.utils.ObjectProxy");
            }
        }
        

        // If we see a tag like <s:Button id="b">, add a VariableDefinition
        // to the class scope, corresponding to the ActionScript code
        //    [Bindable]
        //    public var b:spark.components:Button;
        VariableDefinition variableDefinition = new VariableDefinition(id);
        variableDefinition.setPublic();
        variableDefinition.setTypeReference(typeRef);
        variableDefinition.setNameLocation(idAttribute.getValueStart(), idAttribute.getValueEnd());
        variableDefinition.setBindable();
        currentClassScope.addDefinition(variableDefinition);
    }
    
    public Collection<ICompilerProblem> getProblems()
    {
        return problems;
    }
    
    public TypeScope getClassScope()
    {
        return currentClassScope;
    }
    
    /**
     * @return the includeHandler
     */
    public IncludeHandler getIncludeHandler()
    {
        return includeHandler;
    }
}
