/* * * 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.flex.compiler.internal.parsing.as; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import antlr.Token; import org.apache.flex.abc.ABCConstants; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.internal.definitions.ConstantDefinition; import org.apache.flex.compiler.internal.definitions.DefinitionBase; import org.apache.flex.compiler.internal.parsing.as.ConfigCompilationUnit.ConfigFileNode; import org.apache.flex.compiler.internal.parsing.as.ConfigProcessor.ConfigProject.ConfigProjectScope; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.projects.ISourceFileHandler; import org.apache.flex.compiler.internal.projects.DefinitionPriority.BasePriority; import org.apache.flex.compiler.internal.scopes.ASProjectScope; import org.apache.flex.compiler.internal.semantics.SemanticUtils; import org.apache.flex.compiler.internal.tree.as.ConfigConstNode; import org.apache.flex.compiler.internal.tree.as.ConfigExpressionNode; import org.apache.flex.compiler.internal.tree.as.ConfigNamespaceNode; import org.apache.flex.compiler.internal.tree.as.IdentifierNode; import org.apache.flex.compiler.internal.tree.as.LiteralNode; import org.apache.flex.compiler.internal.tree.as.NamespaceNode; import org.apache.flex.compiler.internal.tree.as.NodeBase; import org.apache.flex.compiler.internal.tree.as.NumericLiteralNode; import org.apache.flex.compiler.internal.tree.as.ScopedBlockNode; import org.apache.flex.compiler.internal.workspaces.Workspace; import org.apache.flex.compiler.problems.CannotResolveConfigExpressionProblem; import org.apache.flex.compiler.problems.CannotResolveProjectLevelConfigExpressionProblem; import org.apache.flex.compiler.problems.ConflictingNameInNamespaceProblem; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.InternalCompilerProblem2; import org.apache.flex.compiler.problems.NonConstantConfigInitProblem; import org.apache.flex.compiler.problems.UndefinedConfigNamespaceProblem; import org.apache.flex.compiler.scopes.IDefinitionSet; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IIdentifierNode; import org.apache.flex.compiler.tree.as.ILiteralNode.LiteralType; import org.apache.flex.compiler.units.ICompilationUnit; import org.apache.flex.compiler.units.requests.ISyntaxTreeRequestResult; import org.apache.flex.compiler.workspaces.IWorkspace; import org.apache.flex.utils.FilenameNormalization; import com.google.common.collect.Iterables; /** * Processor handles config information found by the parser and that is set on * the current project */ public class ConfigProcessor { /** * Used as a fake project to facilitate type lookup */ final class ConfigProject extends org.apache.flex.compiler.internal.projects.ASProject { /** * Scope that allows direct addition of IDefinitions without adding * compilation units */ final class ConfigProjectScope extends ASProjectScope { /** * @param project */ private ConfigProjectScope(CompilerProject project) { super(project); } public void addConfigDefinition(IDefinition definition) { addDefinitionToStore(definition); } } private ConfigProject(IWorkspace w) { super((Workspace)w, true); getSourceCompilationUnitFactory().addHandler(new ISourceFileHandler() { @Override public String[] getExtensions() { return new String[] {"config"}; } @Override public ICompilationUnit createCompilationUnit(CompilerProject project, String path, BasePriority priority, int order, String qname, String locale) { return new ConfigCompilationUnit(backingProject, path); } @Override public boolean needCompilationUnit(CompilerProject project, String path, String qname, String locale) { return true; } @Override public boolean canCreateInvisibleCompilationUnit() { return false; } }); } @Override protected ASProjectScope initProjectScope(CompilerProject project) { return new ConfigProjectScope(project); } @Override public ConfigProjectScope getScope() { return (ConfigProjectScope)super.getScope(); } public void removeCU(ConfigCompilationUnit cu) { removeCompilationUnits(Collections.<ICompilationUnit> singletonList(cu)); } } /** * Scope block that we add all of our config information to */ private ScopedBlockNode configScope; private BaseASParser parser; /** * Flag to keep track of if we should report errors or not If we're * transferring constants from the project to an individual config * processor, then we don't need to report the errors as they were already * reported as errors on the project. */ private boolean transferringConstants; /** * Fake project we use to faciliate type lookup for expressions */ private ConfigProject backingProject; /** * Project variables */ private IProjectConfigVariables variables; /** * Our fake config compilation unit we use to make the type/project system * happy */ private ConfigCompilationUnit configUnit; private HashSet<String> configNames; private final IWorkspace workspace; ConfigProcessor(IWorkspace workspace, BaseASParser parser) { this.parser = parser; configNames = new HashSet<String>(); configNames.add(IASLanguageConstants.DEFAULT_CONFIG_NAME); this.workspace = workspace; } /** * Sets the {@link IProjectConfigVariables} that will be used to alter the * shape of the tree we are building * * @param variables {@link IProjectConfigVariables} or null */ public void connect(IProjectConfigVariables variables) { this.variables = variables; if (variables != null) { List<String> namespaces = variables.getConfigNamespaceNames(); for (String namespace : namespaces) { configNames.add(namespace); } } } /** * Disconnects us from the parser, cleaning up any state that was built */ public void disconnect() { if (configUnit != null) backingProject.removeCU(configUnit); if (backingProject != null) backingProject = null; parser = null; } public final boolean isConfigNamespace(final String name) { return configNames.contains(name); } private final void initConfigStructures() { if (backingProject == null) { backingProject = new ConfigProject(workspace); String configFileName = FilenameNormalization.normalize("config" + Integer.toString(hashCode()) + ".config"); try { backingProject.setIncludeSources(new File[] {new File(configFileName)}); } catch (InterruptedException e1) { //ignore this } Collection<ICompilationUnit> units = backingProject.getCompilationUnits(configFileName); assert units.size() == 1 && Iterables.getOnlyElement(units) instanceof ConfigCompilationUnit; try { transferringConstants = true; ISyntaxTreeRequestResult treeResult = Iterables.getOnlyElement(units).getSyntaxTreeRequest().get(); configScope = ((ConfigFileNode)treeResult.getAST()).getTargetConfigScope(); addConditionalCompilationNamespace(new ConfigNamespaceNode(new IdentifierNode(IASLanguageConstants.DEFAULT_CONFIG_NAME, (Token)null))); if (variables != null) { if (variables != null) { List<IDefinition> definitions = variables.getRequiredDefinitions(); ConfigProjectScope scope = backingProject.getScope(); for (IDefinition def : definitions) { if (def != null && scope.getLocalDefinitionSetByName(def.getQualifiedName()) == null) { scope.addConfigDefinition(def); } } } List<ConfigNamespaceNode> namespaces = variables.getConfigNamespaces(); for (ConfigNamespaceNode ns : namespaces) { addConditionalCompilationNamespace(ns); } List<ConfigConstNode> vars = variables.getConfigVariables(); for (ConfigConstNode var : vars) { addConfigConstNode(var); } } } catch (InterruptedException e) { ICompilerProblem problem = new InternalCompilerProblem2(parser.getFilename(), e, "ConfigProcessor"); addProblem(problem); } finally { transferringConstants = false; } } } /** * Returns any children created by this config processor * * @return children, or an empty array */ public IASNode[] getConfigChildren() { if (configScope != null) { final int childCount = configScope.getChildCount(); ArrayList<IASNode> children = new ArrayList<IASNode>(childCount); for (int i = 0; i < childCount; i++) { NodeBase child = (NodeBase)configScope.getChild(i); ((NodeBase)child).setParent(null); if (child instanceof ConfigConstNode) { ((ConfigConstNode)child).reset(); } children.add(child); } return children.toArray(new IASNode[0]); } return new IASNode[0]; } /** * Adds a name that is recognized as a config name for conditional * compilation */ public boolean addConditionalCompilationNamespace(NamespaceNode node) { initConfigStructures(); IDefinitionSet name = configScope.getASScope().getLocalDefinitionSetByName(node.getName()); if (name != null) { //allow redefinition of config namespace - matches ASC return true; } else { node.normalize(true); configScope.addItemAfterNormalization(node); configScope.getASScope().addDefinition(node.getDefinition()); configNames.add(node.getName()); return true; } } /** * Adds a {@link ConfigConstNode} to our internal tree. Will return true if * this item is unique * * @param node the {@link ConfigConstNode} that we have encountered while * parsing. * @return true if this is not a redefinition of a config name */ public boolean addConfigConstNode(ConfigConstNode node) { initConfigStructures(); node.normalize(true); IdentifierNode configNamespaceNode = (IdentifierNode)node.getNamespaceNode(); IDefinitionSet set = configScope.getASScope().getLocalDefinitionSetByName(configNamespaceNode.getName()); if (set == null) { IIdentifierNode namespaceNode = (IdentifierNode)node.getNamespaceNode(); ICompilerProblem problem = new UndefinedConfigNamespaceProblem(namespaceNode, namespaceNode.getName()); addProblem(problem); return false; } configScope.addItemAfterNormalization(node); DefinitionBase constDef = node.getDefinition(); configScope.getASScope().addDefinition(constDef); if (constDef instanceof ConstantDefinition) { ConstantDefinition def = (ConstantDefinition)constDef; Object value = def.resolveValue(backingProject); if (value == ConfigConstNode.UNKNOWN_VALUE) { // Get the real source node for the problem. // If there isn't one, then don't make a problem - assume // someone else already found the cause and logged it. IASNode problemLocationNode = node.getAssignedValueNode(); if (problemLocationNode != null) { ICompilerProblem problem = new NonConstantConfigInitProblem( problemLocationNode); addProblem(problem); } } } // Check for redeclaration // Config vars don't care about MultiDefinitionType.MANY vs. AMBIGUOUS // and it's not possible for them to shadow params, so we only have to check if // the multi type is not NONE if (SemanticUtils.getMultiDefinitionType(constDef, backingProject) != SemanticUtils.MultiDefinitionType.NONE) { addProblem(new ConflictingNameInNamespaceProblem(node, constDef.getBaseName(), node.getNamespace())); return false; } return true; } /** * Helper method to add a problem * * @param problem Problem to add */ private void addProblem(ICompilerProblem problem) { // If we're transferring the constants to a particular parser, then // the problems have already been reported via the project // don't report them again to avoid dupes if (!transferringConstants) parser.addProblem(problem); } /** * turns a config expression node into a Java Interger, Boolean, etc... * creates a compiler problem is unable to evaluate */ protected Object evaluateConstNodeExpressionToJavaObject(ConfigExpressionNode node) { initConfigStructures(); node.normalize(true); configScope.addItemAfterNormalization(node); Object result = node.resolveConfigValue(backingProject); if (result == null || result == ConfigConstNode.UNKNOWN_VALUE) { // If we can't get a value, log an error. If we are are processing System variables, then don't // use the problem type that requires a "site", as we don't know what it is ICompilerProblem problem = isFromProjectConfigVariables() ? new CannotResolveProjectLevelConfigExpressionProblem(node.getConfigValue()) : new CannotResolveConfigExpressionProblem(node, node.getConfigValue()); addProblem(problem); } return result; } /** * Returns true if we are this config processor was is helping to parse * project variables, like those from the command line. */ private boolean isFromProjectConfigVariables() { return this.parser.isParsingProjectConfigVariables(); } /** * turns a config expression into a synthesize LiteralNode */ protected LiteralNode evaluateConstNodeExpression(ConfigExpressionNode node) { final Object result = evaluateConstNodeExpressionToJavaObject(node); if (result instanceof Boolean) { LiteralNode literalNode = new LiteralNode(LiteralType.BOOLEAN, ((Boolean)result).toString()); literalNode.setSynthetic(true); return literalNode; } else if (result instanceof String) { LiteralNode literalNode = new LiteralNode(LiteralType.STRING, (String)result); literalNode.setSynthetic(true); return literalNode; } else if (result instanceof Double || result instanceof Integer || result instanceof Long) { LiteralNode literalNode = new NumericLiteralNode(result.toString()); literalNode.setSynthetic(true); return literalNode; } else if (result == ABCConstants.NULL_VALUE) { // Handle 'null' LiteralNode literalNode = new LiteralNode(LiteralType.NULL, IASLanguageConstants.Null); literalNode.setSynthetic(true); return literalNode; } return null; } /** * Bind the configurations to a parser. * * @param parser AS parser. */ public final void setParser(BaseASParser parser) { this.parser = parser; } /** * Detach the parser if it's currently bound to this processor. * * @param parser AS parser. */ public final void detachParser(BaseASParser parser) { if (this.parser == parser) this.parser = null; } }