All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.jangaroo.jooc.ast.ClassDeclaration Maven / Gradle / Ivy

The newest version!
/*
 * 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 com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import net.jangaroo.jooc.CompilerError;
import net.jangaroo.jooc.DeclarationScope;
import net.jangaroo.jooc.JangarooParser;
import net.jangaroo.jooc.JooSymbol;
import net.jangaroo.jooc.Jooc;
import net.jangaroo.jooc.Scope;
import net.jangaroo.jooc.config.JoocConfiguration;
import net.jangaroo.jooc.sym;
import net.jangaroo.properties.PropcHelper;
import net.jangaroo.utils.AS3Type;
import net.jangaroo.utils.CompilerUtils;

import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * @author Andreas Gawecki
 * @author Frank Wienberg
 */
public class ClassDeclaration extends TypeDeclaration {

  private static final String OBJECT_CLASSNAME = "Object";

  private JooSymbol symClass;
  private Extends optExtends;
  private Map members = new LinkedHashMap<>();
  private Map staticMembers = new LinkedHashMap<>();
  private ClassBody body;
  private List directives = Collections.emptyList();
  private FunctionDeclaration constructor = null;
  private Type thisType;
  private Type superType;
  private List fieldsWithInitializer = new ArrayList<>();
  private List secondaryDeclarations = Collections.emptyList();
  private String qualifiedNameHash;

  private List assignableClasses;

  private Implements optImplements;
  private Scope scope;

  public ClassDeclaration(AnnotationsAndModifiers am, JooSymbol cls, Ide ide, Extends ext, Implements impl, ClassBody body) {
    super(am, ide);
    this.symClass = cls;
    this.optExtends = ext;
    this.optImplements = impl;
    this.body = body;
  }

  public void setDirectives(List directives) {
    this.directives = directives;
  }

  List getDirectives() {
    return directives;
  }

  @Override
  public List getChildren() {
    return makeChildren(super.getChildren(), optExtends, optImplements, body);
  }

  @Override
  public JooSymbol getDeclarationSymbol() {
    return getSymClass();
  }

  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!
  }

  @Override
  public boolean isPrivate() {
    return super.isPrivate() || !isPrimaryDeclaration(); // secondary classes are considered private statics!
  }

  public String getName() {
    return getIde().getName();
  }

  public void setConstructor(FunctionDeclaration methodDeclaration) {
    if (constructor != null) {
      throw JangarooParser.error(methodDeclaration, "Only one constructor allowed per class");
    }
    constructor = methodDeclaration;
  }

  public JooSymbol getSymClass() {
    return symClass;
  }

  public Extends getOptExtends() {
    return optExtends;
  }

  public Implements getOptImplements() {
    return optImplements;
  }

  public List getFieldsWithInitializer() {
    return fieldsWithInitializer;
  }

  public ClassBody getBody() {
    return body;
  }

  public List getSecondaryDeclarations() {
    return secondaryDeclarations;
  }

  public Map getStaticMembers() {
    return staticMembers;
  }

  @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()));
    fixDefaultSuperClass();
    superType = isInterface() || isObject(getQualifiedNameStr()) ? null
            : new Type(getOptExtends() == null ? new Ide(OBJECT_CLASSNAME) : 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) {
            VariableDeclaration thisDeclaration
                    = new VariableDeclaration(new JooSymbol("var"), new Ide(Ide.THIS), new TypeRelation(null, getThisType()));
            thisDeclaration.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?!
        }
      }
    });
  }

  private boolean hasAssociatedScssFile() {
    JoocConfiguration joocConfiguration = (JoocConfiguration) scope.getCompiler().getConfig();

    for (String sassSourceSubFolderName : joocConfiguration.getSassSourceFilesByType().keySet()) {
      File sassSourceSubFolder = joocConfiguration.getSassSourcePathByType().get(sassSourceSubFolderName);
      Path associatedScssFilePath = Paths.get(sassSourceSubFolder.getPath(), CompilerUtils.fileNameFromQName(getQualifiedNameStr(), File.separatorChar, net.jangaroo.jooc.api.Jooc.SCSS_SUFFIX));
      if (joocConfiguration.getSassSourceFilesByType().get(sassSourceSubFolderName).stream().anyMatch(
              file -> file.toPath().equals(associatedScssFilePath)
      )) {
        return true;
      }
    }
    return false;
  }

  private void fixDefaultSuperClass() {
    // several mixins and plugins don't declare to extend ext.Base, but only implement ext.Plugin. Still, the use the config
    // system defined by ext.Base, so patch "extends ext.Base" in:
    if (optExtends == null && scope.getCompiler().getCompilationUnit("ext.Base") != null) {
      if (!isInterface() &&
              (optImplements != null && (getSuperTypeDeclarations().stream().anyMatch(superType -> superType.isMixin() || "ext.Plugin".equals(superType.getQualifiedNameStr()))))
              || hasAssociatedScssFile()) {
        QualifiedIde extDotBase = new QualifiedIde(new Ide("ext"), new JooSymbol("."), new JooSymbol("Base"));
        new ImportDirective(null, extDotBase, null).scope(scope.getParentScope());
        JooSymbol extendsSymbol = new JooSymbol(sym.EXTENDS, "", -1, -1, " ", "extends");
        optExtends = new Extends(extendsSymbol, extDotBase);
      }
    }
  }

  @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 boolean implementsMoreThanOneInterface() {
    return getAnnotation(Jooc.NATIVE_ANNOTATION_NAME) == null
            && getOptImplements() != null
            && (!(isInterface() || isMixin()) || getOptImplements().getSuperTypes().getTail() != null);
  }

  public void analyze(AstNode parentNode) {
    analyzeSymModifiers();
    super.analyze(parentNode);
    if (getOptExtends() != null) {
      getOptExtends().analyze(this);
    } else if (superType != null) {
      // establish dependency on and import of "Object" compilation unit:
      new Extends(null, superType.getIde()).analyze(this);
    }
    if (getOptImplements() != null) {
      getOptImplements().analyze(this);
    }
    if (implementsMoreThanOneInterface()) {
      scope.getCompilationUnit().addBuiltInIdentifierUsage("mixin");
    }
    body.analyze(this);
    for (IdeDeclaration secondaryDeclaration : secondaryDeclarations) {
      secondaryDeclaration.analyze(this);
    }
  }

  /**
   * Check if interfaces have only modifiers public or internal as described
   * here.
   */
  private void analyzeSymModifiers() {
    if(isInterface()) {
      //noinspection LoopStatementThatDoesntLoop
      for (JooSymbol symModifier : getSymModifiers()) {
        switch (symModifier.sym) {
          case sym.PUBLIC: ;
          case sym.INTERNAL: break;
          default: throw JangarooParser.error(symModifier, "illegal modifier: " + symModifier.getText());
        }
      }
    }
  }

  public void registerMember(TypedIdeDeclaration memberDeclaration) {
    String name = memberDeclaration.getName();
    if (name.length() != 0) {
      Map targetMembers = memberDeclaration.isStatic() ? staticMembers : members;
      TypedIdeDeclaration previousDeclaration = targetMembers.get(name);
      if (previousDeclaration instanceof FunctionDeclaration) {
        FunctionDeclaration previousFunctionDeclaration = (FunctionDeclaration) previousDeclaration;
        if (previousFunctionDeclaration.isGetterOrSetter()) {
          memberDeclaration = PropertyDeclaration.addDeclaration(previousFunctionDeclaration, memberDeclaration);
          if (memberDeclaration == null) {
            // TODO: handle all kinds of errors: two getters, two setters, other duplicate declarations
            // For now, ignore the new member.
            return;
          }
        }
      }

      targetMembers.put(name, memberDeclaration);
    }
  }

  @Override
  public TypedIdeDeclaration getMemberDeclaration(String memberName) {
    return members.get(memberName);
  }

  public Collection getMembers() {
    return members.values();
  }

  public Collection getMethods() {
    return FluentIterable.from(members.values()).transformAndConcat(new Function>() {
      @Nullable
      @Override
      public Iterable apply(@Nullable TypedIdeDeclaration typedIdeDeclaration) {
        return typedIdeDeclaration instanceof FunctionDeclaration ? Collections.singleton((FunctionDeclaration) typedIdeDeclaration)
                : typedIdeDeclaration instanceof PropertyDeclaration ? ((PropertyDeclaration) typedIdeDeclaration).getMethods()
                : Collections.emptyList();
      }
    }).toList();
  }

  @Override
  public TypedIdeDeclaration getStaticMemberDeclaration(String memberName) {
    return staticMembers.get(memberName);
  }

  public boolean isMixin() {
    if (isInterface()) {
      return getAnnotation(Jooc.MIXIN_ANNOTATION_NAME) != null;
    }
    return getMyMixinInterface() != null;
  }

  public ClassDeclaration getMyMixinInterface() {
    if (!isInterface() && getOptImplements() != null) {
      CommaSeparatedList interfaces = getOptImplements().getSuperTypes();
      String myQualifiedName = getQualifiedNameStr();
      while (interfaces != null) {
        Ide oneInterface = interfaces.getHead();
        ClassDeclaration interfaceDeclaration = (ClassDeclaration) oneInterface.getScope().lookupDeclaration(oneInterface);
        Annotation mixinAnnotation = interfaceDeclaration.getAnnotation(Jooc.MIXIN_ANNOTATION_NAME);
        if (mixinAnnotation != null && myQualifiedName.equals(mixinAnnotation.getPropertiesByName().get(null))) {
          return interfaceDeclaration;
        }
        interfaces = interfaces.getTail();
      }
    }
    return null;
  }

  public ClassDeclaration getConfigClassDeclaration() {
    // special cases: ext.Base and all Mixin implementations use Ext Config system, although
    // they don't have the corresponding constructor
    if ("ext.Base".equals(getQualifiedNameStr()) || isMixin()) {
      return this;
    }
    TypeRelation configParameterType = getConstructorConfigParameterType();
    if (configParameterType == null) {
      return null;
    }
    TypeDeclaration declaration =  configParameterType.getType().getDeclaration();
    return declaration instanceof ClassDeclaration && !((ClassDeclaration) declaration).isInterface() &&
            (equals(declaration) || equals(declaration.getSuperTypeDeclaration()) || inheritsFromExtBaseExplicitly())
            ? (ClassDeclaration) declaration  // use given Config type, if Ext class, even an incorrect one
            : null;  // has no Config type
  }

  // check whether this class explicitly inherits from ext.Base, which is a prerequisite to using the Config system
  public boolean inheritsFromExtBaseExplicitly() {
    return inheritsFrom("ext.Base");
  }

  public boolean inheritsFromFlExtEvent() {
    return inheritsFrom("net.jangaroo.ext.FlExtEvent");
  }

  private boolean inheritsFrom(String classQName) {
    for (ClassDeclaration superTypeDeclaration = getSuperTypeDeclaration();
         superTypeDeclaration != null;
         superTypeDeclaration = superTypeDeclaration.getSuperTypeDeclaration()) {
      if (classQName.equals(superTypeDeclaration.getQualifiedNameStr())) {
        return true;
      }
    }
    return false;
  }

  public TypeRelation getConstructorConfigParameterType() {
    FunctionDeclaration constructor = getConstructor();
    if (constructor != null) {
      Parameters params = constructor.getParams();
      while (params != null) {
        Parameter param = params.getHead();
        if ("config".equals(param.getName())) {
          return param.getOptTypeRelation();
        }
        params = params.getTail();
      }
    }
    return null;
  }

  public boolean hasConfigClass() {
    return getConfigClassDeclaration() != null;
  }

  public boolean isSubclassOf(final ClassDeclaration classDeclaration) {
    ClassDeclaration superTypeDeclaration = getSuperTypeDeclaration();
    return superTypeDeclaration != null &&
            (superTypeDeclaration == classDeclaration || superTypeDeclaration.isSubclassOf(classDeclaration)); // NOSONAR no equals here
  }

  public boolean isJavaScriptObject() {
    for (ClassDeclaration classDeclaration = this;
         classDeclaration != null;
         classDeclaration = classDeclaration.getSuperTypeDeclaration()) {
      if ("joo.JavaScriptObject".equals(classDeclaration.getQualifiedNameStr())) {
        return true;
      }
    }
    return false;
  }

  public Type getThisType() {
    return thisType;
  }

  public Type getSuperType() {
    return superType;
  }

  public void setSecondaryDeclarations(List secondaryDeclarations) {
    this.secondaryDeclarations = secondaryDeclarations;
  }

  @Override
  public IdeDeclaration resolveDeclaration() {
    return null;
  }

  public IdeDeclaration resolvePropertyDeclaration(String ide, boolean isStatic) {
    IdeDeclaration declaration = null;
    FunctionDeclaration getterOrSetter = null;
    ensureAssignableClasses();
    for (ClassDeclaration classDecl: assignableClasses) {
      declaration = isStatic ? classDecl.getStaticMemberDeclaration(ide) : classDecl.getMemberDeclaration(ide);
      if (getterOrSetter == null) {
        // we are still looking for getter or setter:
        if (declaration instanceof FunctionDeclaration && ((FunctionDeclaration)declaration).isGetterOrSetter()) {
          // found a getter or setter; remember it:
          getterOrSetter = (FunctionDeclaration) declaration;
          // keep on searching for the complementing setter or getter:
          declaration = null;
        }
      } else {
        // we already found a getter or setter...
        if (declaration != null) {
          // ...and now found another declaration, so try to merge both:
          declaration = PropertyDeclaration.addDeclaration(getterOrSetter, declaration);
        }
      }
      if (declaration != null) {
        return declaration;
      }
    }
    return getterOrSetter;
  }


   private void resolveAssignablesDeclaration1(ClassDeclaration classDecl, List result, Deque chain) {
    if (result.contains(classDecl)) {
      return;
    }
    result.add(classDecl);
    final int chainSize = chain.size();
    chain.add(classDecl);

    IdeDeclaration superTypeDeclaration = null;
    if (!classDecl.isInterface()) {
      if (classDecl.getSuperType() != null) {
        // used to be based on Type, now it can lead to ClassDeclarations not yet available to be loaded twice
        superTypeDeclaration = classDecl.getSuperType().getIde().getDeclaration(false);
      }
    } else if (classDecl.getOptImplements() == null) {
      // it is a top-level interface: simulate inheritance from Object!
      superTypeDeclaration = classDecl.getIde().getScope().getExpressionType(AS3Type.OBJECT).getDeclaration();
    }
    if (superTypeDeclaration != null) {
      resolveAssignablesInSuper(result, chain, superTypeDeclaration);
    }
    if (classDecl.getOptImplements() != null) {
      CommaSeparatedList implemented = classDecl.getOptImplements().getSuperTypes();
      while (implemented != null) {
        resolveAssignablesInSuper(result, chain, implemented.getHead().getDeclaration(false));
        implemented = implemented.getTail();
      }
    }
    chain.removeLast();
    assert chainSize == chain.size();
  }

  private void resolveAssignablesInSuper(final List visited,
                                                final Deque chain,
                                                final IdeDeclaration superClassDecl) {
    if (superClassDecl != null) {
      if (!(superClassDecl instanceof ClassDeclaration)) {
        throw new CompilerError(superClassDecl.getSymbol(), "expected class identifier");
      }
      resolveAssignablesDeclaration1((ClassDeclaration) superClassDecl, visited, chain);
    }
  }

  public boolean isAssignableTo(ClassDeclaration classToCheck) {
    ensureAssignableClasses();

    // TODO should use "|| assignableClasses.contains(classToCheck)" and not iterate over the list
    boolean result = getQualifiedNameStr().equals(classToCheck.getQualifiedNameStr());

    Iterator iterator = assignableClasses.iterator();
    while(!result && iterator.hasNext()) {
      result = iterator.next().getQualifiedNameStr().equals(classToCheck.getQualifiedNameStr());
    }
    return result;
  }

  private void ensureAssignableClasses() {
    if (assignableClasses == null) {
      assignableClasses = new ArrayList<>();
      resolveAssignablesDeclaration1(this, assignableClasses, new LinkedList());
    }
  }

  public boolean notExtendsObject() {
    return superType != null && !isObject(superType.getIde().getQualifiedNameStr());
  }

  public String getQualifiedNameHash() {
    if (qualifiedNameHash == null) {
      qualifiedNameHash = computeQualifiedNameHash();
    }
    return qualifiedNameHash;
  }

  private static final String BASE_64_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$";

  private static String base64Char(int n) {
    int i = n & 63;
    return BASE_64_CHARS.substring(i, i + 1);
  }

  private String computeQualifiedNameHash() {
    int hashCode = getTargetQualifiedNameStr().hashCode();
    return  base64Char(hashCode >>> 18) +
            base64Char(hashCode >>> 12) +
            base64Char(hashCode >>> 6) +
            base64Char(hashCode);
  }

  @Override
  public ClassDeclaration getSuperTypeDeclaration() {
    return superType == null ? null : (ClassDeclaration) superType.getDeclaration();
  }

  public List getSuperTypeDeclarations() {
    List superTypeDeclarations = new ArrayList<>();
    if (getSuperTypeDeclaration() != null) {
      superTypeDeclarations.add(getSuperTypeDeclaration());
    }
    Implements optImplements = getOptImplements();
    if (optImplements != null) {
      CommaSeparatedList superTypes = optImplements.getSuperTypes();
      while (superTypes != null) {
        superTypeDeclarations.add((ClassDeclaration) getIde().getScope().lookupDeclaration(superTypes.getHead()));
        superTypes = superTypes.getTail();
      }
    }
    return superTypeDeclarations;
  }

  public boolean isPropertiesClass() {
    return getName().endsWith(CompilerUtils.PROPERTIES_CLASS_SUFFIX);
  }

  public boolean isPropertiesSubclass() {
    String classname = getName();
    return isPropertiesClass() &&
            classname.substring(0, classname.length() - CompilerUtils.PROPERTIES_CLASS_SUFFIX.length()).contains("_") ;
  }

  @Override
  public String getTargetQualifiedNameStr() {
    if (isPropertiesSubclass()) {
      return PropcHelper.insertNonDefaultLocale(getSuperTypeDeclaration().getTargetQualifiedNameStr(),
              PropcHelper.computeLocale(getQualifiedNameStr()));
    }
    return super.getTargetQualifiedNameStr();
  }

  public void addFieldWithInitializer(VariableDeclaration fieldDeclaration) {
    fieldsWithInitializer.add(fieldDeclaration);
  }

  private static boolean isObject(String fullyQualifiedName) {
    return OBJECT_CLASSNAME.equals(fullyQualifiedName);
  }

  public boolean isObject() {
    return isObject(getQualifiedNameStr());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy