/*
* 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.JooSymbol;
import net.jangaroo.jooc.Jooc;
import net.jangaroo.jooc.Scope;
import java.io.IOException;
/**
* @author Andreas Gawecki
*/
public class Ide extends NodeImplBase {
private JooSymbol ide;
private IdeDeclaration declaration;
private Scope scope;
private Ide qualified;
private boolean bound;
private boolean rewriteThis;
private String packagePrefix = "";
private static final IdeDeclaration NULL_DECL = new VariableDeclaration(null, null, null, null);
public Ide(final String ide) {
this(new JooSymbol(ide));
}
@Override
public void visit(AstVisitor visitor) throws IOException {
visitor.visitIde(this);
}
public Ide(JooSymbol ide) {
this.setIde(ide);
}
public Scope getScope() {
return scope;
}
public JooSymbol getIde() {
return ide;
}
public String getPackagePrefix() {
return packagePrefix;
}
public boolean isThis() {
return "this".equals(getIde().getText());
}
private boolean needsThisAtRuntime() {
if (isSuper()) {
return true;
}
if (!isQualified() && isDeclared()) {
IdeDeclaration decl = getDeclaration();
return decl.isClassMember() && !decl.isStatic();
}
return false;
}
public boolean isSuper() {
return "super".equals(getIde().getText());
}
@Override
public void scope(final Scope scope) {
// scope is "single assignment", allow further assignments to facilitate tree sharing
if (this.scope == null) {
this.scope = scope;
}
}
public String[] getQualifiedName() {
return new String[]{getName()};
}
public String getQualifiedNameStr() {
return getName();
}
public String getName() {
return getIde().getText();
}
public JooSymbol getSymbol() {
return getIde();
}
public boolean isQualifier() {
return qualified != null;
}
public void setQualified(Ide qualifier) {
this.qualified = qualifier;
}
public boolean isQualified() {
return getQualifier() != null;
}
public Ide getQualified() {
return qualified;
}
public Ide getQualifier() {
return null;
}
public boolean isQualifiedByThis() {
return getQualifier() != null && getQualifier().isThis();
}
public boolean isQualifiedBySuper() {
return getQualifier() != null && getQualifier().isSuper();
}
public boolean addExternalUsage() {
IdeDeclaration decl = getDeclaration(false);
if (decl == null || !decl.isPrimaryDeclaration()) {
return false;
}
CompilationUnit currentUnit = getScope().getCompilationUnit();
CompilationUnit compilationUnit = decl.getIde().getScope().getCompilationUnit();
currentUnit.addDependency(compilationUnit);
return true;
}
public void addPublicApiDependency() {
IdeDeclaration decl = getDeclaration(false);
if (decl != null && decl.isPrimaryDeclaration()) {
CompilationUnit currentUnit = getScope().getCompilationUnit();
CompilationUnit compilationUnit = decl.getIde().getScope().getCompilationUnit();
currentUnit.addPublicApiDependency(compilationUnit);
} else if (isQualified()) {
getQualifier().addPublicApiDependency();
}
}
/**
* Resolve the declaration of this ide to the underlying declaration.
* callable after scoping phase
*
* @return null if the declaration cannot be resolved
*/
public IdeDeclaration resolveDeclaration() {
IdeDeclaration decl = getDeclaration(false);
return decl == null ? null : decl.resolveDeclaration();
}
/**
* callable after scoping phase
*
* @throws net.jangaroo.jooc.CompilerError
* if undeclared
*/
public IdeDeclaration getDeclaration() {
return getDeclaration(true);
}
/**
* callable after scoping phase
*/
public IdeDeclaration getDeclaration(boolean errorIfUndeclared) {
if (declaration == null) {
declaration = getScope().lookupDeclaration(this);
if (declaration == null) {
declaration = NULL_DECL; // prevent multiple lookups when called with !errorIfUndeclared multiple times
} else if (declaration.getClassDeclaration() != getScope().getClassDeclaration()) {
if (declaration.isPrivate()) {
throw new CompilerError(this.getSymbol(), "private member access");
}
if (declaration.isProtected() && !getScope().getClassDeclaration().isSubclassOf(declaration.getClassDeclaration())) {
throw new CompilerError(this.getSymbol(), "protected member access of non-superclass");
}
}
}
final IdeDeclaration result = declaration == NULL_DECL ? null : declaration; // NOSONAR no equals here
if (result == null && errorIfUndeclared) {
throw Jooc.error(getIde(), "undeclared identifier '" + getName() + "'");
}
return result;
}
public Ide qualify(final JooSymbol symQualifier, final JooSymbol symDot) {
return new QualifiedIde(new Ide(symQualifier), symDot, getIde());
}
public void analyzeAsExpr(AstNode exprParent, Expr parentExpr) {
if (needsThisAtRuntime()) {
FunctionExpr funExpr = scope.getFunctionExpr();
if (funExpr != null) {
setRewriteThis(funExpr.notifyThisUsed(scope));
}
}
if (isSuper()) {
FunctionDeclaration currentMethod = getScope().getMethodDeclaration();
if (currentMethod == null) {
throw Jooc.error(getIde(), "use of super is only allowed within non-static methods");
}
if (currentMethod.isStatic()) {
throw Jooc.error(getIde(), "use of super inside static method");
}
FunctionExpr currentFunction = getScope().getFunctionExpr();
if (currentFunction.getFunctionDeclaration() != currentMethod) {
throw Jooc.error(getIde(), "super calls might only be used within instance methods, not in local functions");
}
//todo check whether super method exists and is non-static
}
checkDefinedAccessChain();
if (isBoundMethodCandidate(exprParent, parentExpr)) {
IdeDeclaration memberDeclaration = getMemberDeclaration();
// check candidates for instance methods, accessed as function:
if (memberDeclaration != null && memberDeclaration.isMethod() && !((FunctionDeclaration) memberDeclaration).isGetterOrSetter() && !memberDeclaration.isStatic()) {
// check and handle instance methods declared in same file, accessed as function:
getScope().getCompilationUnit().addBuiltInUsage("$$bound");
setBound(true);
}
}
if (scope != null) {
usageInExpr(exprParent);
if (!isThis() && !isQualified()) {
IdeDeclaration decl = getDeclaration(false);
// add package prefix if it is not a local
if (decl != null) {
if (!decl.isClassMember() && decl.getParentDeclaration() instanceof PackageDeclaration) {
String qName = decl.getPackageDeclaration().getQualifiedNameStr();
if (qName.length() > 0) {
String auxVarForPackage = scope.getCompilationUnit().getAuxVarForPackage(scope, qName);
packagePrefix = auxVarForPackage + ".";
}
} else if ((!isQualifier() || exprParent instanceof ApplyExpr)
&& !(exprParent instanceof ArrayIndexExpr) && (decl instanceof Parameter)) {
FunctionExpr currentFunction = scope.getFunctionExpr();
if (currentFunction != null) {
currentFunction.notifyArgumentsUsed(decl);
}
}
}
}
}
}
private void usageInExpr(final AstNode exprParent) {
if (isThis()) {
FunctionExpr funExpr = getScope().getFunctionExpr();
if (funExpr != null && funExpr.getFunctionDeclaration() == null) {
FunctionDeclaration methodDeclaration = getScope().getMethodDeclaration();
if (methodDeclaration != null && !methodDeclaration.isStatic() && !isArgumentOfTypeCast(exprParent)) {
Jooc.warning(getSymbol(), "'this' may be unbound and is untyped in functions, even inside methods. Consider removing 'this.' (members are in scope!) or refactoring inner function to method.");
}
}
}
addExternalUsage();
//todo handle references to static super members
// check access to another class or a constant of another class; other class then must be initialized:
if (!(exprParent instanceof NewExpr) && !(exprParent instanceof IsExpr) && !(exprParent instanceof AsExpr)) {
ClassDeclaration classDeclaration = getScope().getClassDeclaration();
if (classDeclaration != null) {
if (isQualified()) {
// access to constant of other class?
// TODO: If the static member is a method, we should not add a class init.
// Unfortunately, declaration information seems not to be available at this point in time.
if (exprParent instanceof ApplyExpr) {
// It seems to be a method. Static methods take care of initializing their class,
// but methods of plackage-scope variables do not initialize the compilation unit.
// Thus, we only add initialization if the compilation unit is a package-scope variable.
classDeclaration.addInitIfGlobalVar(getQualifier());
} else {
classDeclaration.addInitIfClassOrGlobalVar(getQualifier());
}
}
// access to other class?
if (!isQualifier()) {
classDeclaration.addInitIfClassOrGlobalVar(this);
}
}
}
}
private boolean isArgumentOfTypeCast(AstNode parentNode) {
if (parentNode instanceof CommaSeparatedList) {
AstNode argumentsCandidate = parentNode.getParentNode();
if (argumentsCandidate instanceof ParenthesizedExpr) {
AstNode typeCastCandidate = argumentsCandidate.getParentNode();
return typeCastCandidate instanceof ApplyExpr && ((ApplyExpr)typeCastCandidate).isTypeCast();
}
} else if (parentNode instanceof AsExpr) {
return ((AsExpr)parentNode).getArg1() == getParentNode();
}
return false;
}
public IdeDeclaration getMemberDeclaration() {
IdeDeclaration ideDeclaration = getDeclaration(false);
if (ideDeclaration != null && ideDeclaration.isClassMember()) {
return ideDeclaration;
}
return ideDeclaration;
}
private void checkDefinedAccessChain() {
if (!isQualified() && //this method is called for every node of a qualified ide tree, so we rely on the call on the root ide
!isDeclared() && !isValidPackageAccessChain()) {
throw Jooc.error(getIde(), "undeclared identifier '" + getName() + "'");
}
}
private boolean isValidPackageAccessChain() {
if (isQualifier()) {
final Ide qualifiedIde = getQualified();
return qualifiedIde.isDeclared() || qualifiedIde.isValidPackageAccessChain();
}
return false;
}
private boolean isDeclared() {
return getDeclaration(false) != null;
}
private boolean isBoundMethodCandidate(final AstNode exprParent, final Expr parentExpr) {
return exprParent instanceof ParenthesizedExpr ||
exprParent instanceof CommaSeparatedList ||
exprParent instanceof Initializer ||
exprParent instanceof AsExpr ||
exprParent.getClass().equals(BinaryOpExpr.class) ||
exprParent instanceof ObjectField ||
exprParent instanceof ReturnStatement ||
(exprParent instanceof AssignmentOpExpr && ((AssignmentOpExpr) exprParent).getArg2() == parentExpr);
}
public boolean usePrivateMemberName(IdeDeclaration memberDeclaration) {
return isQualifiedBySuper()
&& scope.getClassDeclaration().getMemberDeclaration(getName()) != null
|| memberDeclaration.isPrivate();
}
public static IdeDeclaration resolveMember(final IdeDeclaration type, final Ide memberIde) {
IdeDeclaration declaration = null;
if (type != null) {
declaration = type.resolvePropertyDeclaration(memberIde.getName());
}
return declaration;
}
@Override
public String toString() {
return getQualifiedNameStr();
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Ide ide1 = (Ide) o;
return !(getIde() != null ? !getIde().getText().equals(ide1.getIde().getText()) : ide1.getIde() != null);
}
@Override
public int hashCode() {
int result = getIde() != null ? getIde().hashCode() : 0;
result = 31 * result + (scope != null ? scope.hashCode() : 0);
return result;
}
public void setIde(JooSymbol ide) {
this.ide = ide;
}
public boolean isBound() {
return bound;
}
public void setBound(boolean bound) {
this.bound = bound;
}
public boolean isRewriteThis() {
return rewriteThis;
}
public void setRewriteThis(boolean rewriteThis) {
this.rewriteThis = rewriteThis;
}
}