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

net.jangaroo.jooc.model.CompilationUnitModelRegistry Maven / Gradle / Ivy

There is a newer version: 4.1.17
Show newest version
package net.jangaroo.jooc.model;

import net.jangaroo.jooc.backend.ActionScriptCodeGeneratingModelVisitor;
import net.jangaroo.utils.AS3Type;

import javax.annotation.Nonnull;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A registry of all known classes/interfaces. Lookup by name.
 */
public class CompilationUnitModelRegistry extends CompilationUnitModelResolver {
  private Map registry = new LinkedHashMap<>(500);
  private final ActionScriptCodeGeneratingModelVisitor DEBUG_CODE_GENERATOR = new ActionScriptCodeGeneratingModelVisitor(new PrintWriter(System.err), true);

  public void register(CompilationUnitModel compilationUnitModel) {
    String qName = compilationUnitModel.getQName();
    if (registry.containsKey(qName)) {
      throw new IllegalArgumentException("Attempt to redefine " + qName);
    }
    registry.put(qName, compilationUnitModel);
  }

  public Collection getCompilationUnitModels() {
    return registry.values();
  }

  // TODO: add built-in classes to registry's "classpath"!
  private static ClassModel createErrorClass() {
    ClassModel errorClass = new ClassModel("Error");
    errorClass.addMember(new MethodModel("Error", "Error", new ParamModel("msg", "String", "null")));
    errorClass.addMember(new MethodModel("toString", "String"));
    return errorClass;
  }
  private static final CompilationUnitModel ERROR_COMPILATION_UNIT = new CompilationUnitModel("", createErrorClass());

  @Override
  public CompilationUnitModel resolveCompilationUnit(@Nonnull String qName) {
    if ("Error".equals(qName)) {
      return ERROR_COMPILATION_UNIT;
    }
    return registry.get(qName);
  }

  private MethodModel resolveConstructor(ClassModel classModel) {
    return resolveMethod(classModel, null, null);
  }

  private CompilationUnitModel resolveDefiningType(CompilationUnitModel classModel, MethodType methodType, String name) {
    CompilationUnitModel definingType = null;
    if (classModel != null) {
      definingType = resolveDefiningInterface(classModel, methodType, name);
      if (definingType == null) {
        definingType = resolveDefiningClass(classModel, methodType, name);
      }
    }
    return definingType;
  }

  private CompilationUnitModel resolveDefiningInterface(CompilationUnitModel compilationUnitModel, MethodType methodType, String methodName) {
    CompilationUnitModel definingInterface = null;
    ClassModel classModel = compilationUnitModel.getClassModel();
    CompilationUnitModel superclass = getSuperclassCompilationUnit(classModel);
    if (superclass != null) {
      definingInterface = resolveDefiningInterface(superclass, methodType, methodName);
    }
    // look in current class's interfaces:
    for (String interfaceName : classModel.getInterfaces()) {
      CompilationUnitModel anInterface = resolveCompilationUnit(interfaceName);
      CompilationUnitModel recursionResult = resolveDefiningInterface(anInterface, methodType, methodName);
      if (recursionResult != null && (definingInterface == null || implementsInterface(definingInterface, recursionResult.getQName()))) {
        // found more general interface that defines the method:
        definingInterface = recursionResult;
      }
    }
    // only if nothing found yet, and current class is actually an interface, we bother to look locally:
    if (definingInterface == null && classModel.isInterface() && classModel.getMethod(methodType, methodName) != null) {
      definingInterface = compilationUnitModel;
    }
    return definingInterface;
  }

  private CompilationUnitModel resolveDefiningClass(CompilationUnitModel classCompilationUnit, MethodType methodType, String methodName) {
    CompilationUnitModel definingClass = null;
    CompilationUnitModel currentCompilationUnit = classCompilationUnit;
    while (currentCompilationUnit != null) {
      ClassModel classModel = currentCompilationUnit.getClassModel();
      MethodModel method = classModel.getMethod(methodType, methodName == null ? classModel.getName() : methodName);
      if (method != null) {
        definingClass = currentCompilationUnit;
        // don't return immediately, but search for root definition!
      }
      currentCompilationUnit = getSuperclassCompilationUnit(classModel);
    }
    return definingClass;
  }

  public MethodModel resolveMethod(ClassModel classModel, MethodType methodType, String methodName) {
    ClassModel currentClass = classModel;
    while (currentClass != null) {
      MethodModel method = currentClass.getMethod(methodType, methodName == null ? currentClass.getName() : methodName);
      if (method != null) {
        return method;
      }
      currentClass = getSuperclass(currentClass);
    }
    return null;
  }

  public void complementOverrides() {
    // remove overrides in interfaces (not supported by AS3!):
    for (CompilationUnitModel compilationUnitModel : getCompilationUnitModels()) {
      ClassModel classModel = compilationUnitModel.getClassModel();
      if (classModel != null && classModel.isInterface()) {
        Set toBeRemoved = new HashSet<>();
        for (MemberModel memberModel : classModel.getMembers()) {
          if (memberModel.isMethod()) { // should all be methods -- it's an interface!
            CompilationUnitModel definingInterfaceCU =
                    resolveDefiningInterface(compilationUnitModel, ((MethodModel) memberModel).getMethodType(),
                            memberModel.getName());
            if (definingInterfaceCU != compilationUnitModel) {
              toBeRemoved.add(memberModel);
            }
          }
        }
        for (MemberModel memberModel : toBeRemoved) {
          classModel.removeMember(memberModel);
        }
      }
    }

    // add all missing implementations of interface methods:
    for (CompilationUnitModel compilationUnitModel : getCompilationUnitModels()) {
      ClassModel classModel = compilationUnitModel.getClassModel();
      if (classModel != null && !classModel.isInterface()) {
        CompilationUnitModel superclass = getSuperclassCompilationUnit(classModel);
        for (String anInterface : classModel.getInterfaces()) {
          if (!implementsInterface(superclass, anInterface)) {
            CompilationUnitModel interfaceModel = resolveCompilationUnit(anInterface);
            for (MemberModel memberModel : interfaceModel.getClassModel().getMembers()) {
              if (memberModel.isMethod()) {
                addImplementingMethod(classModel, (MethodModel)memberModel);
              } else {
                PropertyModel propertyModel = (PropertyModel)memberModel;
                if (propertyModel.isReadable()) {
                  addImplementingMethod(classModel, propertyModel.getGetter());
                }
                if (propertyModel.isWritable()) {
                  addImplementingMethod(classModel, propertyModel.getSetter());
                }
              }
            }
          }
        }
      }
    }
    // add constructors and "override" flags where necessary, and correct overriding methods' signatures:
    for (CompilationUnitModel compilationUnitModel : getCompilationUnitModels()) {
      ClassModel classModel = compilationUnitModel.getClassModel();
      if (classModel != null && getSuperclass(classModel) != null) {
        MethodModel constructor = complementConstructor(classModel);
        for (MemberModel memberModel : classModel.getMembers()) {
          if (memberModel != constructor && !memberModel.isField() && !memberModel.isStatic()) {
            if (memberModel.isMethod()) {
              complementOverridingMethod(compilationUnitModel, (MethodModel)memberModel);
            } else {
              complementOverridingProperty(compilationUnitModel, (PropertyModel)memberModel);
            }
          }
        }
      }
    }
  }

  private void addImplementingMethod(ClassModel classModel, MethodModel methodModel) {
    String memberName = methodModel.getName();
    if (resolveMethod(classModel, methodModel.getMethodType(), memberName) == null) {
      MethodModel implementingMethod = methodModel.duplicate();
      implementingMethod.setAsdoc("@inheritDoc");
      MemberModel oldMember = classModel.addMember(implementingMethod);
      if (oldMember != null) {
        System.err.println("[WARN] To implement method " + memberName + " in class " + classModel.getName() +
                ", we have to remove a property of type " + oldMember.getType() + " with the same name!");
      }
    }
  }

  private MethodModel complementConstructor(ClassModel classModel) {
    if (classModel.isInterface()) {
      return null;
    }
    ClassModel superclass = getSuperclass(classModel);
    MethodModel constructor = classModel.getConstructor();
    if (constructor != null && constructor.getBody() != null) {
      return constructor;
    }
    MethodModel superclassConstructor = resolveConstructor(superclass);
    if (superclassConstructor != null) {
      List params = superclassConstructor.getParams();
      StringBuilder superCallValues = new StringBuilder();
      for (ParamModel superParam : params) {
        if (superParam.isOptional()) {
          break;
        }
        if (superCallValues.length() > 0) {
          superCallValues.append(", ");
        }
        superCallValues.append(AS3Type.getDefaultValue(superParam.getType()));
      }
      if (constructor == null) {
        constructor = classModel.createConstructor();
        constructor.setAsdoc("@inheritDoc");
        // Usually, if there is no explicit constructor, all parameters are handed on to the super constructor.
        // However, since we also want to support using an empty parameter list, we set all parameters to "optional".
        for (ParamModel superParam : params) {
          ParamModel paramModel = superParam.duplicate();
          paramModel.setOptional(true);
          constructor.addParam(paramModel);
        }
      }
      constructor.setBody("super(" + superCallValues + ");");
    }
    if (constructor != null && constructor.getBody() == null) {
      // no super constructor found, generate simple super call body:
      constructor.setBody("super();");
    }
    return constructor;
  }

  private void complementOverridingMethod(CompilationUnitModel compilationUnitModel, MethodModel methodModel) {
    ClassModel classModel = compilationUnitModel.getClassModel();
    if (classModel == null) {
      return;
    }
    String methodName = methodModel.getName();
    MethodModel superclassMethod = resolveMethod(getSuperclass(classModel), methodModel.getMethodType(), methodName);
    if (superclassMethod != null) {
      methodModel.setOverride(true);
    }
    CompilationUnitModel definingType = resolveDefiningType(compilationUnitModel, methodModel.getMethodType(), methodName);
    if (!definingType.equals(compilationUnitModel)) {
      MethodModel methodSignature = definingType.getClassModel().getMethod(methodModel.getMethodType(), methodName);
      if (!methodSignature.equals(methodModel)) {
        // correct method signature:
        logMethodSignatureCorrection(definingType, methodSignature, compilationUnitModel, methodModel);
        methodModel.setParams(methodSignature.getParams()); // TODO: pull up optional parameters/non-void return type into signature?
        methodModel.setType(methodSignature.getType());
      }
    }
  }

  private void complementOverridingProperty(CompilationUnitModel compilationUnitModel, PropertyModel propertyModel) {
    if (propertyModel.isReadable()) {
      complementOverridingProperty(compilationUnitModel, propertyModel, MethodType.GET);
    }
    if (propertyModel.isWritable()) {
      complementOverridingProperty(compilationUnitModel, propertyModel, MethodType.SET);
    }
  }

  private void complementOverridingProperty(CompilationUnitModel compilationUnitModel, PropertyModel propertyModel, MethodType methodType) {
    ClassModel classModel = compilationUnitModel.getClassModel();
    String propertyName = propertyModel.getName();
    MethodModel superclassAccessor = resolveMethod(getSuperclass(classModel), methodType, propertyName);
    MethodModel accessor = propertyModel.getMethod(methodType);
    if (superclassAccessor != null) {
      accessor.setOverride(true);
    }
    CompilationUnitModel definingType = resolveDefiningType(compilationUnitModel, methodType, propertyName);
    if (!definingType.equals(compilationUnitModel)) {
      PropertyModel superPropertyModel = definingType.getClassModel().getProperty(propertyModel.isStatic(), propertyName);
      if (!accessor.getType().equals(superPropertyModel.getType())) {
        // correct property type:
        logMethodSignatureCorrection(definingType, definingType.getClassModel().getMethod(methodType, propertyName), compilationUnitModel, accessor);
        propertyModel.setType(superPropertyModel.getType());
      }
    }
  }

  private void logMethodSignatureCorrection(CompilationUnitModel definingType, MethodModel methodSignature, CompilationUnitModel classModel, MethodModel methodModel) {
    // skip logging if the following conditions hold (obviously, return type/params have just been forgotten in overriding method):
    // * return type matches OR is void while defined to be something else AND
    // * parameters are empty
    if ((methodModel.getType() == null || methodModel.getType().equals("void") || methodModel.getType().equals(methodSignature.getType()))
      && methodModel.getParams().isEmpty()) {
      return;
    }
    System.err.println("*** corrected ERROR in " + methodSignature.getMethodType() + " signature of " + classModel.getQName() + "." + methodSignature.getName() + " deviates from definition in " + definingType.getQName() + ".");
    System.err.printf(" %s:%n", definingType.getQName());
    DEBUG_CODE_GENERATOR.setCompilationUnitModel(definingType);
    methodSignature.visit(DEBUG_CODE_GENERATOR);
    DEBUG_CODE_GENERATOR.flush();
    System.err.printf(" %s:%n", classModel.getQName());
    DEBUG_CODE_GENERATOR.setCompilationUnitModel(classModel);
    methodModel.visit(DEBUG_CODE_GENERATOR);
    DEBUG_CODE_GENERATOR.flush();
  }

  public void complementImports() {
    for (CompilationUnitModel compilationUnitModel : getCompilationUnitModels()) {
      compilationUnitModel.addImplicitImports();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy