blob: d5ef15158cfa7819c4184b7ce446c7771c53ca64 [file] [log] [blame]
/*
*
* 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.codegen.js.jx;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.apache.royale.compiler.asdoc.royale.ASDocComment;
import org.apache.royale.compiler.codegen.ISubEmitter;
import org.apache.royale.compiler.codegen.js.IJSEmitter;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.*;
import org.apache.royale.compiler.internal.codegen.as.ASEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.JSSessionModel.ImplicitBindableImplementation;
import org.apache.royale.compiler.internal.codegen.js.JSSubEmitter;
import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleEmitter;
import org.apache.royale.compiler.internal.codegen.js.royale.JSRoyaleEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.goog.JSGoogEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.node.NodeEmitterTokens;
import org.apache.royale.compiler.internal.codegen.js.utils.EmitterUtils;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition.INamepaceDeclarationDirective;
import org.apache.royale.compiler.internal.projects.RoyaleJSProject;
import org.apache.royale.compiler.internal.scopes.ASProjectScope;
import org.apache.royale.compiler.internal.scopes.PackageScope;
import org.apache.royale.compiler.internal.tree.as.ClassNode;
import org.apache.royale.compiler.internal.tree.as.InterfaceNode;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.targets.ITarget.TargetType;
import org.apache.royale.compiler.tree.as.ITypeNode;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.utils.NativeUtils;
import org.apache.royale.compiler.utils.NodeJSUtils;
public class PackageHeaderEmitter extends JSSubEmitter implements
ISubEmitter<IPackageDefinition>
{
public PackageHeaderEmitter(IJSEmitter emitter)
{
super(emitter);
}
@Override
public void emit(IPackageDefinition definition)
{
RoyaleJSProject project = (RoyaleJSProject) getProject();
IASScope containedScope = definition.getContainedScope();
ITypeDefinition type = EmitterUtils.findType(containedScope
.getAllLocalDefinitions());
String qname = null;
boolean isExterns = false;
if (type != null)
{
qname = type.getQualifiedName();
ITypeNode typeNode = type.getNode();
if (typeNode instanceof ClassNode)
{
ClassNode classNode = (ClassNode) typeNode;
ASDocComment asDoc = (ASDocComment) classNode.getASDocComment();
if (asDoc != null)
{
String asDocString = asDoc.commentNoEnd();
isExterns = asDocString.contains(JSRoyaleEmitterTokens.EXTERNS.getToken());
getEmitter().getModel().isExterns = isExterns;
}
}
else if (typeNode instanceof InterfaceNode)
{
InterfaceNode interfaceNode = (InterfaceNode) typeNode;
ASDocComment asDoc = (ASDocComment) interfaceNode.getASDocComment();
if (asDoc != null)
{
String asDocString = asDoc.commentNoEnd();
isExterns = asDocString.contains(JSRoyaleEmitterTokens.EXTERNS.getToken());
getEmitter().getModel().isExterns = isExterns;
}
}
}
if (qname == null)
{
IFunctionDefinition fn = EmitterUtils.findFunction(containedScope
.getAllLocalDefinitions());
if(fn != null)
{
qname = fn.getQualifiedName();
}
}
if (qname == null)
{
IVariableDefinition variable = EmitterUtils.findVariable(containedScope
.getAllLocalDefinitions());
if(variable != null)
{
qname = variable.getQualifiedName();
}
}
if (qname == null)
{
INamepaceDeclarationDirective ns = EmitterUtils.findNamespace(containedScope
.getAllLocalDefinitions());
if(ns != null)
{
qname = ns.getQualifiedName();
}
}
if (qname == null)
{
return;
}
List<File> sourcePaths = project.getSourcePath();
String sourceName = definition.getSourcePath();
for (File sourcePath : sourcePaths)
{
if (sourceName.startsWith(sourcePath.getAbsolutePath()))
{
sourceName = sourceName.substring(sourcePath.getAbsolutePath().length() + 1);
}
}
writeNewline("/**");
writeNewline(" * Generated by Apache Royale Compiler from " + sourceName.replace('\\', '/'));
writeNewline(" * " + qname);
writeNewline(" *");
writeNewline(" * @fileoverview");
if (isExterns)
writeNewline(" * @externs");
writeNewline(" *");
// need to suppress access controls so access to protected/private from defineProperties
// doesn't generate warnings.
writeNewline(" * @suppress {checkTypes|accessControls}");
writeNewline(" */");
writeNewline();
if (!isExterns)
{
/* goog.provide('x');\n\n */
write(JSGoogEmitterTokens.GOOG_PROVIDE);
write(ASEmitterTokens.PAREN_OPEN);
write(ASEmitterTokens.SINGLE_QUOTE);
write(((JSRoyaleEmitter)getEmitter()).formatQualifiedName(qname, true));
write(ASEmitterTokens.SINGLE_QUOTE);
write(ASEmitterTokens.PAREN_CLOSE);
writeNewline(ASEmitterTokens.SEMICOLON);
HashMap<String, String> internalClasses = getEmitter().getModel().getInternalClasses();
if (internalClasses.size() > 0)
{
ArrayList<String> classesInOrder = new ArrayList<String>();
for (String internalClass : internalClasses.keySet())
{
classesInOrder.add(internalClass);
}
Collections.sort(classesInOrder);
for (String internalClass : classesInOrder)
{
/* goog.provide('x');\n\n */
write(JSGoogEmitterTokens.GOOG_PROVIDE);
write(ASEmitterTokens.PAREN_OPEN);
write(ASEmitterTokens.SINGLE_QUOTE);
write(((JSRoyaleEmitter)getEmitter()).formatQualifiedName(internalClass, true));
write(ASEmitterTokens.SINGLE_QUOTE);
write(ASEmitterTokens.PAREN_CLOSE);
writeNewline(ASEmitterTokens.SEMICOLON);
}
}
}
else
{
String pkgName = definition.getQualifiedName();
if (pkgName.length() > 0)
{
String[] parts = pkgName.split("\\.");
String current = "";
boolean firstOne = true;
for (String part : parts)
{
current += part;
writeNewline("/** @const */");
if (firstOne)
{
write("var ");
firstOne = false;
}
write(current);
write(" = {}");
writeNewline(ASEmitterTokens.SEMICOLON);
current += ".";
}
}
}
writeNewline();
}
public void emitContents(IPackageDefinition definition)
{
if (getEmitter().getModel().isExterns) return;
// TODO (mschmalle) will remove this cast as more things get abstracted
JSRoyaleEmitter fjs = (JSRoyaleEmitter) getEmitter();
PackageScope containedScope = (PackageScope) definition
.getContainedScope();
ArrayList<String> writtenRequires = new ArrayList<String>();
Collection<IDefinition> localDefinitions = containedScope.getAllLocalDefinitions();
ITypeDefinition type = EmitterUtils.findType(localDefinitions);
IDefinition otherMainDefinition = null;
if (type == null)
{
if (localDefinitions.isEmpty())
return;
// function or variable definition
otherMainDefinition = localDefinitions.iterator().next();
}
else
{
ITypeNode typeNode = type.getNode();
if (typeNode instanceof ClassNode)
{
ClassNode classNode = (ClassNode) typeNode;
ASDocComment asDoc = (ASDocComment) classNode.getASDocComment();
if (asDoc != null)
{
String asDocString = asDoc.commentNoEnd();
String ignoreToken = JSRoyaleEmitterTokens.IGNORE_IMPORT
.getToken();
int ignoreIndex = asDocString.indexOf(ignoreToken);
while (ignoreIndex != -1)
{
String ignorable = asDocString.substring(ignoreIndex
+ ignoreToken.length());
int endIndex = ignorable.indexOf("\n");
ignorable = ignorable.substring(0, endIndex);
ignorable = ignorable.trim();
// pretend we've already written the goog.requires for this
writtenRequires.add(ignorable);
ignoreIndex = asDocString.indexOf(ignoreToken,
ignoreIndex + ignoreToken.length());
}
}
}
}
RoyaleJSProject royaleProject = (RoyaleJSProject) getProject();
ASProjectScope projectScope = (ASProjectScope) royaleProject.getScope();
ICompilationUnit cu = projectScope
.getCompilationUnitForDefinition(type != null ? type : otherMainDefinition);
ArrayList<String> requiresList = royaleProject.getRequires(cu);
ArrayList<String> interfacesList = royaleProject.getInterfaces(cu);
ArrayList<String> externalRequiresList = royaleProject.getExternalRequires(cu);
String cname = (type != null) ? type.getQualifiedName() : otherMainDefinition.getQualifiedName();
writtenRequires.add(cname); // make sure we don't add ourselves
if (type instanceof IClassDefinition) {
//check whether we should add the requires for the implicit Bindable EventDispatcher implementations
boolean needsBindableSupport = ((IClassDefinition) type).needsEventDispatcher(royaleProject);
if (needsBindableSupport) {
IClassDefinition bindableClassDef = (IClassDefinition) type;
ClassDefinition objectClassDefinition = (ClassDefinition)royaleProject.getBuiltinType(
IASLanguageConstants.BuiltinType.OBJECT);
if (bindableClassDef.resolveBaseClass(royaleProject).equals(objectClassDefinition)) {
//keep the decision in the model for later
getModel().registerImplicitBindableImplementation( bindableClassDef,
ImplicitBindableImplementation.EXTENDS);
// add the requiresList support for extending the dispatcher class
if (!requiresList.contains(fjs.formatQualifiedName(BindableEmitter.DISPATCHER_CLASS_QNAME))) {
requiresList.add(fjs.formatQualifiedName(BindableEmitter.DISPATCHER_CLASS_QNAME));
}
} else {
//keep the decision in the model for later
getModel().registerImplicitBindableImplementation( bindableClassDef,
ImplicitBindableImplementation.IMPLEMENTS);
//add the requiresList support for implementing IEventDispatcher
if (!requiresList.contains(fjs.formatQualifiedName(BindableEmitter.DISPATCHER_INTERFACE_QNAME))) {
requiresList.add(fjs.formatQualifiedName(BindableEmitter.DISPATCHER_INTERFACE_QNAME));
}
if (!requiresList.contains(fjs.formatQualifiedName(BindableEmitter.DISPATCHER_CLASS_QNAME))) {
requiresList.add(fjs.formatQualifiedName(BindableEmitter.DISPATCHER_CLASS_QNAME));
}
}
}
if (!needsBindableSupport) {
//we still need to check for static-only bindable requirements. If it was also instance-bindable,
//then the static-only requirements have already been met above
needsBindableSupport = ((IClassDefinition) type).needsStaticEventDispatcher(royaleProject);
//static-only bindable *only* requires the Dispatcher class, not the interface
if (needsBindableSupport
&& !requiresList.contains(fjs.formatQualifiedName(BindableEmitter.DISPATCHER_CLASS_QNAME))) {
requiresList.add(fjs.formatQualifiedName(BindableEmitter.DISPATCHER_CLASS_QNAME));
}
}
}
boolean emitsRequires = emitRequires(requiresList, writtenRequires, cname, royaleProject);
boolean emitsInterfaces = emitInterfaces(interfacesList, writtenRequires);
// erikdebruin: Add missing language feature support, with e.g. 'is' and
// 'as' operators. We don't need to worry about requiring
// this in every project: ADVANCED_OPTIMISATIONS will NOT
// include any of the code if it is not used in the project.
boolean makingSWC = royaleProject.getSWFTarget() != null &&
royaleProject.getSWFTarget().getTargetType() == TargetType.SWC;
boolean isMainCU = royaleProject.mainCU != null
&& cu.getName().equals(royaleProject.mainCU.getName());
if (isMainCU || makingSWC)
{
ICompilerProject project = this.getProject();
if (project instanceof RoyaleJSProject)
{
if (((RoyaleJSProject)project).needLanguage)
{
write(JSGoogEmitterTokens.GOOG_REQUIRE);
write(ASEmitterTokens.PAREN_OPEN);
write(ASEmitterTokens.SINGLE_QUOTE);
write(JSRoyaleEmitterTokens.LANGUAGE_QNAME);
write(ASEmitterTokens.SINGLE_QUOTE);
write(ASEmitterTokens.PAREN_CLOSE);
writeNewline(ASEmitterTokens.SEMICOLON);
}
}
}
boolean emitsExternalRequires = emitExternalRequires(externalRequiresList, writtenRequires);
if (emitsRequires || emitsInterfaces || emitsExternalRequires || isMainCU)
{
writeNewline();
}
writeNewline();
writeNewline();
}
private boolean emitRequires(List<String> requiresList, List<String> writtenRequires, String cname, RoyaleJSProject project)
{
boolean emitsRequires = false;
if (requiresList != null)
{
Collections.sort(requiresList);
for (String imp : requiresList)
{
if (imp.contains(JSGoogEmitterTokens.AS3.getToken()))
continue;
if (imp.equals(JSGoogEmitterTokens.GOOG_BIND.getToken()))
continue;
if (imp.equals(cname))
continue;
if (NativeUtils.isNative(imp))
{
if (!(imp.equals("QName") || imp.equals("Namespace") || imp.equals("XML") || imp.equals("XMLList")))
continue;
}
if (NativeUtils.isJSNative(imp))
{
continue;
}
if(!project.isGoogProvided(imp))
{
continue;
}
if (writtenRequires.indexOf(imp) == -1)
{
/* goog.require('x');\n */
write(JSGoogEmitterTokens.GOOG_REQUIRE);
write(ASEmitterTokens.PAREN_OPEN);
write(ASEmitterTokens.SINGLE_QUOTE);
write(((JSRoyaleEmitter)getEmitter()).formatQualifiedName(imp, true));
write(ASEmitterTokens.SINGLE_QUOTE);
write(ASEmitterTokens.PAREN_CLOSE);
writeNewline(ASEmitterTokens.SEMICOLON);
writtenRequires.add(imp);
emitsRequires = true;
}
}
}
return emitsRequires;
}
private boolean emitInterfaces(List<String> interfacesList, List<String> writtenRequires)
{
boolean emitsInterfaces = false;
if (interfacesList != null)
{
Collections.sort(interfacesList);
for (String imp : interfacesList)
{
if (writtenRequires.indexOf(imp) == -1)
{
write(JSGoogEmitterTokens.GOOG_REQUIRE);
write(ASEmitterTokens.PAREN_OPEN);
write(ASEmitterTokens.SINGLE_QUOTE);
write(((JSRoyaleEmitter)getEmitter()).formatQualifiedName(imp, true));
write(ASEmitterTokens.SINGLE_QUOTE);
write(ASEmitterTokens.PAREN_CLOSE);
writeNewline(ASEmitterTokens.SEMICOLON);
emitsInterfaces = true;
}
}
}
return emitsInterfaces;
}
private boolean emitExternalRequires(List<String> externalRequiresList, List<String> writtenRequires)
{
boolean emitsExternalRequires = false;
if (externalRequiresList != null)
{
Collections.sort(externalRequiresList);
for (String nodeJSModuleName : externalRequiresList)
{
if (writtenRequires.indexOf(nodeJSModuleName) == -1)
{
String moduleVariableName = NodeJSUtils.convertFromDashesToCamelCase(nodeJSModuleName);
/* var x = require('x');\n */
/* var someModule = require('some-module');\n */
write(ASEmitterTokens.VAR);
write(ASEmitterTokens.SPACE);
write(moduleVariableName);
write(ASEmitterTokens.SPACE);
write(ASEmitterTokens.EQUAL);
write(ASEmitterTokens.SPACE);
write(NodeEmitterTokens.REQUIRE);
write(ASEmitterTokens.PAREN_OPEN);
write(ASEmitterTokens.SINGLE_QUOTE);
write(nodeJSModuleName);
write(ASEmitterTokens.SINGLE_QUOTE);
write(ASEmitterTokens.PAREN_CLOSE);
writeNewline(ASEmitterTokens.SEMICOLON);
writtenRequires.add(nodeJSModuleName);
emitsExternalRequires = true;
}
}
}
return emitsExternalRequires;
}
}