blob: 7817e7ce78ac7b16698d3a9c3213655a7bb2a13b [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.typedefs.reference;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.royale.compiler.internal.codegen.typedefs.pass.AbstractCompilerPass;
import org.apache.royale.compiler.internal.codegen.typedefs.utils.DebugLogUtils;
import org.apache.royale.compiler.internal.codegen.typedefs.utils.JSTypeUtils;
import org.apache.royale.compiler.internal.codegen.typedefs.DummyNode;
import org.apache.royale.compiler.problems.UnresolvedClassReferenceProblem;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
public class ClassReference extends BaseReference
{
private boolean isFinal;
private boolean isDynamic;
private String moduleName;
private int enumConstantCounter = 0;
private Set<String> imports = new HashSet<String>();
private MethodReference constructor;
private Map<String, FieldReference> instanceFields = new HashMap<String, FieldReference>();
private Map<String, FieldReference> staticFields = new HashMap<String, FieldReference>();
private Map<String, MethodReference> instanceMethods = new HashMap<String, MethodReference>();
private Map<String, MethodReference> staticMethods = new HashMap<String, MethodReference>();
private Node nameNode;
private Node functionNode;
@SuppressWarnings("unused")
private Node paramListNode;
private boolean isNamespace;
public final int getEnumConstant()
{
return enumConstantCounter;
}
public final void nextEnumConstant()
{
enumConstantCounter++;
}
public void setIsNamespace(boolean isNamespace)
{
this.isNamespace = isNamespace;
}
public boolean isNamespace()
{
return isNamespace;
}
public MethodReference getConstructor()
{
return constructor;
}
public ArrayList<FieldReference> getAllFields()
{
ArrayList<FieldReference> allMethods = new ArrayList<FieldReference>();
if (!isInterface())
allMethods.addAll(staticFields.values());
allMethods.addAll(instanceFields.values());
return allMethods;
}
public ArrayList<MethodReference> getAllMethods()
{
ArrayList<MethodReference> allMethods = new ArrayList<MethodReference>();
if (!isInterface())
allMethods.addAll(staticMethods.values());
allMethods.addAll(instanceMethods.values());
return allMethods;
}
public FieldReference getStaticField(String name)
{
return staticFields.get(name);
}
public FieldReference getInstanceField(String name)
{
return instanceFields.get(name);
}
public MethodReference getStaticMethod(String name)
{
return staticMethods.get(name);
}
public MethodReference getInstanceMethod(String name)
{
return instanceMethods.get(name);
}
public boolean isDynamic()
{
return isDynamic;
}
public void setDynamic(boolean isDynamic)
{
this.isDynamic = isDynamic;
}
public boolean isFinal()
{
return isFinal;
}
public void setFinal(boolean isFinal)
{
this.isFinal = isFinal;
}
public String getModuleName()
{
return moduleName;
}
public void setModuleName(String moduleName)
{
this.moduleName = moduleName;
}
public final boolean isInterface()
{
return getComment().isInterface();
}
/**
*
* @param model
* @param node (FUNCTION [NAME, PARAM_LIST, BLOCK]), or (ASSIGN [FUNCTION [NAME, PARAM_LIST, BLOCK]])
* @param qualifiedName
*/
public ClassReference(ReferenceModel model, Node node, String qualifiedName)
{
super(model, node, qualifiedName, node.getJSDocInfo());
indent = "";
nameNode = null;
functionNode = null;
paramListNode = null;
if (comment.hasEnumParameterType())
{
/*
var Foo = { ...
VAR 35 [jsdoc_info: JSDocInfo]
NAME FontFaceSetLoadStatus
OBJECTLIT
STRING_KEY LOADED
STRING loaded
STRING_KEY LOADING
STRING loading
Or..
foo.bar.baz.QualifiedEnum = { ...
ASSIGN 50 [jsdoc_info: JSDocInfo]
GETPROP
GETPROP
...
STRING QualifiedEnum
OBJECTLIT 50
*/
String overrideStringType = JSTypeUtils.toEnumTypeString(this);
Node objLit = null;
if (node.isVar())
{
objLit = node.getFirstChild().getFirstChild();
}
else if (node.isAssign())
{
objLit = node.getLastChild();
}
if (objLit != null)
{
for (Node stringKey : objLit.children())
{
if (stringKey.isStringKey())
{
Node valueNode = stringKey.getFirstChild();
JSDocInfoBuilder b = new JSDocInfoBuilder(true);
JSDocInfo fieldComment = b.build();
String fieldName = stringKey.getString();
FieldReference field = addField(stringKey, fieldName, fieldComment, true);
field.setConst(true);
field.setOverrideStringType(overrideStringType);
field.setConstantValueNode(valueNode);
}
}
}
}
else if (comment.getTypedefType() != null)
{
JSTypeExpression typeDefType = comment.getTypedefType();
JSType typeDefJSType = model.evaluate(typeDefType);
if (typeDefJSType != null)
{
ObjectType typeDefObjectType = typeDefJSType.toObjectType();
if (typeDefObjectType != null)
{
Map<String,JSType> properties = typeDefObjectType.getPropertyTypeMap();
for (Map.Entry<String, JSType> property : properties.entrySet())
{
JSDocInfoBuilder b = new JSDocInfoBuilder(true);
b.recordBlockDescription("Generated doc for missing field JSDoc.");
JSDocInfo fieldComment = b.build();
addField(node, property.getKey(), fieldComment, false);
}
}
}
//System.out.println(node.toStringTree());
/*
VAR 727 [jsdoc_info: JSDocInfo] [source_file: [w3c_rtc]] [length: 21]
NAME MediaConstraints 727 [source_file: [w3c_rtc]] [length: 16]
*/
}
else if (comment.isConstant())
{
/*
VAR 882 [jsdoc_info: JSDocInfo]
NAME Math
OBJECTLIT
*/
constructor = new NullConstructorReference(model, this, node, getBaseName(), comment);
}
else if (node.isFunction())
{
/*
FUNCTION FooVarArgs 43 [jsdoc_info: JSDocInfo]
NAME FooVarArgs
PARAM_LIST
NAME arg1
NAME var_args
BLOCK 43
*/
nameNode = node.getChildAtIndex(0);
functionNode = node;
paramListNode = functionNode.getChildAtIndex(1);
}
else if (node.isVar())
{
/*
VAR 67 [jsdoc_info: JSDocInfo]
NAME VarAssignFooNoArgs
FUNCTION
NAME
PARAM_LIST
BLOCK
*/
nameNode = node.getChildAtIndex(0);
functionNode = nameNode.getChildAtIndex(0);
try
{
paramListNode = functionNode.getChildAtIndex(1);
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else if (node.isAssign() && node.getChildAtIndex(1).isFunction())
{
/*
ASSIGN 60 [jsdoc_info: JSDocInfo]
NAME AssignFooNoArgs
FUNCTION
NAME
PARAM_LIST
BLOCK
*/
nameNode = node.getFirstChild();
functionNode = node.getLastChild();
// this is an anonymous function assignment, no name
//functionNameNode = functionNode.getChildAtIndex(0);
paramListNode = functionNode.getChildAtIndex(1);
}
if (functionNode != null)
{
constructor = new MethodReference(model, this, functionNode, getBaseName(), comment, false);
}
moduleName = model.getConfiguration().isNamedModule(this);
}
private static List<String> definedPackages = new ArrayList<String>();
@Override
public void emit(StringBuilder sb)
{
enumConstantCounter = 0;
String packageName = getPackageName();
if (outputJS)
{
sb.append("/** @fileoverview Auto-generated Externs files\n * @externs\n */\n");
if (!packageName.isEmpty())
{
if (!definedPackages.contains(packageName))
{
definedPackages.add(packageName);
String[] pieces = packageName.split("\\.");
String chain = "";
int n = pieces.length;
for (int i = 0; i < n; i++)
{
String piece = pieces[i];
sb.append("\n");
sb.append("\n");
sb.append("/**\n * @const\n * @suppress {duplicate|const} */\n");
if (chain.isEmpty())
sb.append("var " + piece + " = {};\n\n\n");
else
sb.append(chain + "." + piece + " = {}\n\n\n");
chain = chain + "." + piece;
}
}
}
}
else
{
sb.append("package ");
if (!packageName.equals(""))
sb.append(packageName).append(" ");
sb.append("{\n");
sb.append("\n");
emitImports(sb);
}
if (moduleName != null)
{
sb.append("[JSModule");
if (packageName.length() > 0 || !getBaseName().equals(moduleName))
{
sb.append("(");
sb.append("name=\"");
sb.append(moduleName);
sb.append("\"");
sb.append(")");
}
sb.append("]");
sb.append("\n");
}
emitComment(sb);
boolean isInterface = isInterface();
if (isInterface)
{
emitInterface(sb);
}
else
{
emitClass(sb);
}
if (!outputJS)
{
sb.append("{\n");
sb.append("\n");
}
if (!isInterface)
{
emitConstructor(sb);
sb.append("\n");
}
emitFields(sb);
emitMethods(sb);
if (!outputJS)
{
sb.append("}\n");
sb.append("}\n"); // package
}
}
public boolean hasSuperField(String fieldName)
{
List<ClassReference> list = getSuperClasses();
for (ClassReference reference : list)
{
if (reference.hasInstanceField(fieldName))
return true;
}
return false;
}
public boolean hasSuperMethod(String methodName)
{
List<ClassReference> list = getSuperClasses();
for (ClassReference reference : list)
{
if (reference.hasInstanceMethod(methodName))
return true;
}
return false;
}
public MethodReference getSuperMethod(String methodName)
{
List<ClassReference> list = getSuperClasses();
for (ClassReference reference : list)
{
if (reference.hasInstanceMethod(methodName))
return reference.getInstanceMethod(methodName);
}
list = getAllImplInterfaces(); // return all our interfaces and all superclass
for (ClassReference reference : list)
{
if (reference.hasInstanceMethod(methodName))
return reference.getInstanceMethod(methodName);
}
return null;
}
public List<ClassReference> getSuperClasses()
{
ArrayList<ClassReference> result = new ArrayList<ClassReference>();
if (isInterface())
{
return getExtendedInterfaces();
}
ClassReference superClass = getSuperClass();
while (superClass != null)
{
result.add(superClass);
superClass = superClass.getSuperClass();
}
return result;
}
public List<ClassReference> getAllImplInterfaces()
{
ArrayList<ClassReference> result = new ArrayList<ClassReference>();
for (JSTypeExpression jsTypeExpression : getComment().getImplementedInterfaces())
{
String interfaceName = getModel().evaluate(jsTypeExpression).getDisplayName();
ClassReference classReference = getModel().getClassReference(interfaceName);
if (classReference != null)
result.add(classReference);
}
return result;
}
public List<ClassReference> getImplementedInterfaces()
{
ArrayList<ClassReference> result = new ArrayList<ClassReference>();
for (JSTypeExpression jsTypeExpression : getComment().getImplementedInterfaces())
{
String interfaceName = getModel().evaluate(jsTypeExpression).toAnnotationString();
ClassReference reference = getModel().getClassReference(interfaceName);
if (reference != null)
result.add(reference);
}
return result;
}
public List<ClassReference> getExtendedInterfaces()
{
ArrayList<ClassReference> result = new ArrayList<ClassReference>();
for (JSTypeExpression jsTypeExpression : getComment().getExtendedInterfaces())
{
String interfaceName = getModel().evaluate(jsTypeExpression).toAnnotationString();
ClassReference reference = getModel().getClassReference(interfaceName);
if (reference != null)
result.add(reference);
}
return result;
}
public List<ClassReference> getInterfaces()
{
ArrayList<ClassReference> result = new ArrayList<ClassReference>();
List<JSTypeExpression> implementedInterfaces = getComment().getImplementedInterfaces();
for (JSTypeExpression jsTypeExpression : implementedInterfaces)
{
JSType jsType = getModel().evaluate(jsTypeExpression);
String interfaceName = jsType.toAnnotationString();
ClassReference interfaceReference = getModel().getClassReference(interfaceName);
if (interfaceReference != null)
result.add(interfaceReference);
else
{
DummyNode node = new DummyNode();
String externName = AbstractCompilerPass.getSourceFileName(this.getNode().getStaticSourceFile().getName(), getModel());
node.setSourcePath(externName);
node.setLine(this.getNode().getLineno());
UnresolvedClassReferenceProblem problem = new UnresolvedClassReferenceProblem(node, interfaceName);
getModel().problems.add(problem);
}
}
return result;
}
public List<ClassReference> getSuperInterfaces()
{
ArrayList<ClassReference> result = new ArrayList<ClassReference>();
result.addAll(getInterfaces());
ClassReference superClass = getSuperClass();
while (superClass != null)
{
result.addAll(superClass.getInterfaces());
superClass = superClass.getSuperClass();
}
return result;
}
public boolean hasInstanceField(String fieldName)
{
return instanceFields.containsKey(fieldName);
}
public boolean hasStaticField(String fieldName)
{
return staticFields.containsKey(fieldName);
}
public boolean hasInstanceMethod(String fieldName)
{
return instanceMethods.containsKey(fieldName);
}
public boolean hasStaticMethod(String fieldName)
{
return staticMethods.containsKey(fieldName);
}
public FieldReference addField(Node node, String fieldName, JSDocInfo comment, boolean isStatic)
{
if (isStatic ? hasStaticField(fieldName) : hasInstanceField(fieldName))
{
// XXX Warning
return null;
}
/* AJH This doesn't make sense to me
if (isNamespace)
isStatic = false;
*/
if (comment == null)
{
DebugLogUtils.err("Field comment null for; " + node.getQualifiedName());
//DebugLogUtils.err(node);
JSDocInfoBuilder b = new JSDocInfoBuilder(true);
b.recordBlockDescription("Generated doc for missing field JSDoc.");
comment = b.build();
}
FieldReference field = new FieldReference(getModel(), this, node, fieldName, comment, isStatic);
if (isStatic)
staticFields.put(fieldName, field);
else
instanceFields.put(fieldName, field);
return field;
}
public MethodReference addMethod(Node node, String functionName, JSDocInfo comment, boolean isStatic)
{
/* AJH This doesn't make sense to me
if (isNamespace)
isStatic = false;
*/
if (comment == null)
{
DebugLogUtils.err("Method comment null for; " + node.getQualifiedName());
//DebugLogUtils.err(node);
JSDocInfoBuilder b = new JSDocInfoBuilder(true);
b.recordBlockDescription("Generated doc for missing method JSDoc.");
comment = b.build();
}
MethodReference method = new MethodReference(getModel(), this, node, functionName, comment, isStatic);
if (isStatic)
{
staticMethods.put(functionName, method);
}
else if (getQualifiedName().equals("Object") && functionName.equals("toString"))
{
// skipping Object.prototype.toString() allows toString(opt_radix) for Number, int and uint
}
else
{
instanceMethods.put(functionName, method);
}
return method;
}
public boolean isMethodOverrideFromInterface(MethodReference reference)
{
boolean isMethodOverrideFromInterface = false;
if (!hasImplementations())
{
List<JSTypeExpression> implementedInterfaces = getComment().getImplementedInterfaces();
for (JSTypeExpression jsTypeExpression : implementedInterfaces)
{
String interfaceName = getModel().evaluate(jsTypeExpression).getDisplayName();
ClassReference classReference = getModel().getClassReference(interfaceName);
if (classReference.hasSuperMethod(reference.getQualifiedName()))
{
isMethodOverrideFromInterface = true;
break;
}
}
}
return isMethodOverrideFromInterface;
}
public MethodReference getMethodOverrideFromInterface(MethodReference reference)
{
// get all super classes, reverse and search top down
List<ClassReference> superClasses = getSuperClasses();
superClasses.add(0, this);
Collections.reverse(superClasses);
// for each superclass, get all implemented interfaces
for (ClassReference classReference : superClasses)
{
List<ClassReference> interfaces = classReference.getImplementedInterfaces();
for (ClassReference interfaceReference : interfaces)
{
// check for the method on the interface
MethodReference method = interfaceReference.getInstanceMethod(reference.getBaseName());
if (method != null)
return method;
}
}
return null;
}
public ClassReference getSuperClass()
{
if (getBaseName().equals("Object"))
return null;
JSTypeExpression baseType = getComment().getBaseType();
if (baseType != null)
{
JSType jsType = getModel().evaluate(baseType);
if (jsType != null)
return getModel().getClassReference(jsType.getDisplayName());
}
else
{
return getModel().getObjectReference();
}
return null;
}
public boolean hasSuperFieldConflict(FieldReference reference)
{
// ClassReference2 superClass = getSuperClass();
// if (superClass != null)
// return superClass.getInstanceFields().containsKey(
// reference.getName());
return false;
}
public boolean isPropertyInterfaceImplementation(String fieldName)
{
List<ClassReference> superInterfaces = getSuperInterfaces();
for (ClassReference interfaceRef : superInterfaces)
{
if (interfaceRef == null)
{
System.err.println("isPropertyInterfaceImplementation() null");
continue;
}
if (interfaceRef.hasInstanceField(fieldName))
return true;
}
return false;
}
public boolean hasLocalMethodConflict(String functionName)
{
return instanceMethods.containsKey(functionName) || staticMethods.containsKey(functionName);
}
public void addImport(ClassReference reference)
{
if (reference != null)
{
imports.add(reference.getQualifiedName());
}
}
public boolean hasImport(String qualifiedName)
{
return imports.contains(qualifiedName);
}
private boolean hasImplementations()
{
return getComment().getImplementedInterfaceCount() > 0;
}
private void emitImports(StringBuilder sb)
{
if (imports.size() > 0)
{
for (String anImport : imports)
{
sb.append("import ").append(anImport).append(";\n");
}
sb.append("\n");
}
}
@Override
protected void emitCommentBody(StringBuilder sb)
{
super.emitCommentBody(sb);
super.emitParams(sb);
if (isInterface())
sb.append(" * @interface\n");
else
sb.append(" * @constructor\n");
if (getComment().hasBaseType())
{
sb.append(" * @");
emitSuperClass(sb);
sb.append("\n");
}
if (!isInterface())
{
if (!outputJS)
sb.append(" * @");
emitImplements(sb);
sb.append("\n");
List<JSTypeExpression> implementedInterfaces = getComment().getImplementedInterfaces();
int len = implementedInterfaces.size();
if (len != 0)
{
for (int i = 0; i < len; i++)
{
String value = getModel().evaluate(implementedInterfaces.get(i)).getDisplayName();
if (value.equals("IArrayLike"))
{
String comment = getComment().getOriginalCommentString();
int c = comment.indexOf("IArrayLike");
int c1 = comment.indexOf('<', c);
if (c1 == c + 10)
{
int c2 = comment.indexOf('>', c1);
if (c2 != -1)
{
String type = comment.substring(c1 + 1, c2);
int insert = sb.toString().indexOf("\n");
sb.insert(insert + 1, "\n[ArrayElementType(\"" + JSTypeUtils.transformType(type) + "\")]\n");
}
}
break;
}
}
}
}
}
private void emitClass(StringBuilder sb)
{
if (outputJS)
return;
sb.append("public ");
if (isDynamic)
{
sb.append("dynamic ");
}
if (isFinal)
{
sb.append("final ");
}
sb.append("class ");
sb.append(getBaseName()).append(" ");
if (getComment().hasBaseType())
{
emitSuperClass(sb);
sb.append(" ");
}
else
{
// XXX JSObject extends
//sb.append("extends JSObject ");
}
if (!isInterface())
{
emitImplements(sb);
}
}
private void emitInterface(StringBuilder sb)
{
sb.append("public interface ");
sb.append(getBaseName()).append(" ");
List<JSTypeExpression> extendedInterfaces = getComment().getExtendedInterfaces();
int len = extendedInterfaces.size();
if (len > 0)
{
sb.append("extends ");
for (JSTypeExpression jsTypeExpression : extendedInterfaces)
{
String value = getModel().evaluate(jsTypeExpression).toAnnotationString();
sb.append(value);
if (--len > 0)
sb.append(", ");
}
sb.append(" ");
}
}
private void emitSuperClass(StringBuilder sb)
{
if (outputJS)
{
sb.append("extends ");
String value = JSTypeUtils.toClassTypeString(this);
sb.append(value);
sb.append("\n");
}
else
{
sb.append("extends ");
String value = JSTypeUtils.toClassTypeString(this);
sb.append(value);
}
}
private void emitImplements(StringBuilder sb)
{
List<JSTypeExpression> implementedInterfaces = getComment().getImplementedInterfaces();
if (implementedInterfaces.size() == 0)
return;
if (outputJS)
sb.append(" * @implements ");
else
sb.append("implements ");
int len = implementedInterfaces.size();
for (int i = 0; i < len; i++)
{
String value = getModel().evaluate(implementedInterfaces.get(i)).getDisplayName();
sb.append(value);
if (outputJS)
{
if (i < len - 1)
sb.append("\n * @implements ");
else
sb.append("\n");
}
else
{
if (i < len - 1)
sb.append(", ");
}
}
sb.append(" ");
}
private void emitConstructor(StringBuilder sb)
{
if (constructor != null)
{
constructor.emit(sb);
}
}
private void emitFields(StringBuilder sb)
{
for (FieldReference field : getAllFields())
{
field.emit(sb);
sb.append("\n");
nextEnumConstant();
}
}
private void emitMethods(StringBuilder sb)
{
for (MethodReference method : getAllMethods())
{
method.emit(sb);
sb.append("\n");
}
}
public File getFile(File asSourceRoot)
{
File jsRoot = getModel().getConfiguration().getJsRoot();
if (jsRoot == null)
{
String packagePath = toPackagePath();
return new File(asSourceRoot, packagePath + File.separator + getBaseName() + ".as");
}
return new File(jsRoot, getBaseName() + ".js");
}
private String toPackagePath()
{
String packageName = getPackageName();
String[] cname = packageName.split("\\.");
String sdirPath = "";
if (cname.length > 0)
{
for (final String aCname : cname)
{
sdirPath += aCname + File.separator;
}
return sdirPath;
}
return "";
}
}