/*
* Copyright 2008 CoreMedia AG
*
* Licensed 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 net.jangaroo.jooc.ast;
import net.jangaroo.jooc.CompilerError;
import net.jangaroo.jooc.DeclarationScope;
import net.jangaroo.jooc.JooSymbol;
import net.jangaroo.jooc.Jooc;
import net.jangaroo.jooc.Scope;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Andreas Gawecki
* @author Frank Wienberg
*/
public class ClassDeclaration extends IdeDeclaration {
private JooSymbol symClass;
private Extends optExtends;
private Map<String, TypedIdeDeclaration> members = new LinkedHashMap<String, TypedIdeDeclaration>();
private Map<String, TypedIdeDeclaration> staticMembers = new LinkedHashMap<String, TypedIdeDeclaration>();
private Set<String> classInit = new HashSet<String>();
private ClassBody body;
private FunctionDeclaration constructor = null;
private Type thisType;
private Type superType;
private List<VariableDeclaration> fieldsWithInitializer = new ArrayList<VariableDeclaration>();
private List<IdeDeclaration> secondaryDeclarations = Collections.emptyList();
private int inheritanceLevel = -1;
private Implements optImplements;
private Scope scope;
public ClassDeclaration(JooSymbol[] modifiers, JooSymbol cls, Ide ide, Extends ext, Implements impl, ClassBody body) {
super(modifiers, ide);
this.symClass = cls;
this.optExtends = ext;
this.optImplements = impl;
this.body = body;
}
@Override
public List<? extends AstNode> getChildren() {
return makeChildren(super.getChildren(), optExtends, optImplements, body);
}
public FunctionDeclaration getConstructor() {
return constructor;
}
@Override
public void visit(AstVisitor visitor) throws IOException {
visitor.visitClassDeclaration(this);
}
@Override
protected int getAllowedModifiers() {
return MODIFIER_ABSTRACT | MODIFIER_FINAL | MODIFIERS_SCOPE | MODIFIER_STATIC | MODIFIER_DYNAMIC;
}
public boolean isInterface() {
return "interface".equals(getSymClass().getText());
}
public boolean isAbstract() {
return isInterface() || super.isAbstract();
}
@Override
public boolean isStatic() {
return super.isStatic() || !isPrimaryDeclaration(); // secondary classes are always static!
}
@Override
public boolean isClassMember() {
return super.isClassMember() || !isPrimaryDeclaration(); // secondary classes are (static) class members!
}
public String getName() {
return getIde().getName();
}
public void setConstructor(FunctionDeclaration methodDeclaration) {
if (constructor != null) {
throw Jooc.error(methodDeclaration, "Only one constructor allowed per class");
}
// if (methodDeclaration != body.declararations.get(0))
// Jooc.error(methodDeclaration, "Constructor declaration must be the first declaration in a class");
constructor = methodDeclaration;
}
public JooSymbol getSymClass() {
return symClass;
}
public Extends getOptExtends() {
return optExtends;
}
public Implements getOptImplements() {
return optImplements;
}
public List<VariableDeclaration> getFieldsWithInitializer() {
return fieldsWithInitializer;
}
public ClassBody getBody() {
return body;
}
public List<IdeDeclaration> getSecondaryDeclarations() {
return secondaryDeclarations;
}
public Map<String, TypedIdeDeclaration> getStaticMembers() {
return staticMembers;
}
public Set<String> getClassInit() {
return classInit;
}
@Override
public void scope(final Scope scope) {
this.scope = scope;
// this declares this class's ide:
super.scope(scope);
// define these here so they get the right scope:
thisType = new Type(new Ide(getIde().getSymbol()));
superType = "Object".equals(getQualifiedNameStr()) ? null
: new Type(getOptExtends() == null ? new Ide("Object") : getOptExtends().getSuperClass());
thisType.scope(scope);
if (superType != null) {
superType.scope(scope);
}
if (getOptImplements() != null) {
getOptImplements().scope(scope);
}
// one scope for static members...
withNewDeclarationScope(this, scope, new Scoped() {
@Override
public void run(final Scope staticScope) {
// ...and one scope for instance members!
withNewDeclarationScope(ClassDeclaration.this, staticScope, new Scoped() {
@Override
public void run(final Scope instanceScope) {
//todo ugly, maybe we should define ClassScope implements Scope to lookup inherited members
if(instanceScope instanceof DeclarationScope) {
((DeclarationScope) instanceScope).setIsInstanceScope(true);
}
body.scope(staticScope, instanceScope);
}
});
for (IdeDeclaration secondaryDeclaration : secondaryDeclarations) {
secondaryDeclaration.scope(staticScope); //todo is this the correct scope?!
}
}
});
}
@Override
public void handleDuplicateDeclaration(final Scope scope, final AstNode oldNode) {
// allow same package import of this class
if (!(oldNode instanceof ImportDirective)) { //todo check for same package import
super.handleDuplicateDeclaration(scope, oldNode);
}
}
public void analyze(AstNode parentNode) {
super.analyze(parentNode);
if (getOptExtends() != null) {
getOptExtends().analyze(this);
String packageName = getOptExtends().getSuperClass().getDeclaration().getPackageDeclaration().getQualifiedNameStr();
if (packageName.length() > 0 && parentNode instanceof CompilationUnit) {
((CompilationUnit)parentNode).getAuxVarForPackage(scope, packageName);
}
}
if (getOptImplements() != null) {
getOptImplements().analyze(this);
}
body.analyze(this);
for (IdeDeclaration secondaryDeclaration : secondaryDeclarations) {
secondaryDeclaration.analyze(this);
}
}
public void registerMember(TypedIdeDeclaration memberDeclaration) {
String name = memberDeclaration.getName();
if (name.length() != 0) {
(memberDeclaration.isStatic() ? staticMembers : members).put(name, memberDeclaration);
}
}
public TypedIdeDeclaration getMemberDeclaration(String memberName) {
return members.get(memberName);
}
public TypedIdeDeclaration getStaticMemberDeclaration(String memberName) {
return staticMembers.get(memberName);
}
public void addInitIfGlobalVar(Ide ide) {
addInitIf(ide, false);
}
public void addInitIfClassOrGlobalVar(Ide ide) {
addInitIf(ide, true);
}
private void addInitIf(Ide ide, boolean allowClass) {
final IdeDeclaration decl = ide.getDeclaration(false);
if (decl != this // Classes should not try to init themselves. It does not help and it produces strange warnings.
&& (allowClass && decl instanceof ClassDeclaration || decl instanceof VariableDeclaration) // no init necessary for package-scope functions!
&& decl.isPrimaryDeclaration()) {
CompilationUnit compilationUnit = decl.getIde().getScope().getCompilationUnit();
if (compilationUnit.getAnnotation(Jooc.NATIVE_ANNOTATION_NAME) == null) {
classInit.add(decl.getQualifiedNameStr());
}
}
}
public boolean isSubclassOf(final ClassDeclaration classDeclaration) {
ClassDeclaration superTypeDeclaration = getSuperTypeDeclaration();
return superTypeDeclaration != null &&
(superTypeDeclaration == classDeclaration || superTypeDeclaration.isSubclassOf(classDeclaration)); // NOSONAR no equals here
}
public Type getThisType() {
return thisType;
}
public Type getSuperType() {
return superType;
}
public void setSecondaryDeclarations(List<IdeDeclaration> secondaryDeclarations) {
this.secondaryDeclarations = secondaryDeclarations;
}
@Override
public IdeDeclaration resolveDeclaration() {
return this;
}
/**
* Lookup a non-static member of the given name
*
* @param ide the member name
* @return a non-static member if found, null otherwise
*/
public IdeDeclaration resolvePropertyDeclaration(String ide) {
return resolvePropertyDeclaration1(ide, this, new HashSet<ClassDeclaration>(), new LinkedList<ClassDeclaration>());
}
private IdeDeclaration resolvePropertyDeclaration1(String ide, ClassDeclaration classDecl, Set<ClassDeclaration> visited, Deque<ClassDeclaration> chain) {
if (visited.contains(classDecl)) {
if (chain.contains(classDecl)) {
throw new CompilerError(classDecl.getSymbol(), "cyclic superclass chain");
}
return null;
}
visited.add(classDecl);
final int chainSize = chain.size();
chain.add(classDecl);
IdeDeclaration declaration = classDecl.getMemberDeclaration(ide);
if (declaration == null) {
declaration = classDecl.getStaticMemberDeclaration(ide);
}
if (declaration == null && classDecl.getSuperType() != null) {
declaration = resolvePropertyInSuper(ide, classDecl, visited, chain, classDecl.getSuperType().getIde());
}
if (declaration == null && classDecl.getOptImplements() != null) {
CommaSeparatedList<Ide> implemented = classDecl.getOptImplements().getSuperTypes();
while (implemented != null && declaration == null) {
declaration = resolvePropertyInSuper(ide, classDecl, visited, chain, implemented.getHead());
implemented = implemented.getTail();
}
}
chain.removeLast();
assert chainSize == chain.size();
return declaration;
}
private IdeDeclaration resolvePropertyInSuper(final String ide,
final ClassDeclaration classDecl,
final Set<ClassDeclaration> visited,
final Deque<ClassDeclaration> chain,
final Ide superIde) {
IdeDeclaration superClassDecl = superIde.getDeclaration(false);
if (superClassDecl != null) {
if (!(superClassDecl instanceof ClassDeclaration)) {
throw new CompilerError(classDecl.getOptExtends().getSuperClass().getSymbol(), "expected class identifier");
}
return resolvePropertyDeclaration1(ide, (ClassDeclaration) superClassDecl, visited, chain);
}
return null;
}
public int getInheritanceLevel() {
if (inheritanceLevel < 0) {
inheritanceLevel = computeInheritanceLevel();
}
return inheritanceLevel;
}
private int computeInheritanceLevel() {
if (superType == null) {
return 0;
}
if ("Object".equals(superType.getIde().getQualifiedNameStr())) {
return 1;
}
IdeDeclaration superClassDecl = superType.getIde().getDeclaration();
if (!(superClassDecl instanceof ClassDeclaration)) {
throw new CompilerError(getOptExtends().getSuperClass().getSymbol(), "expected class identifier");
}
return 1 + ((ClassDeclaration) superClassDecl).getInheritanceLevel();
}
public ClassDeclaration getSuperTypeDeclaration() {
return superType == null ? null : (ClassDeclaration) superType.getIde().getDeclaration();
}
public void addFieldWithInitializer(VariableDeclaration fieldDeclaration) {
fieldsWithInitializer.add(fieldDeclaration);
}
}