/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.compiler.java.codegen;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Interface;
import com.redhat.ceylon.model.typechecker.model.Function;
import com.redhat.ceylon.model.typechecker.model.Setter;
import com.redhat.ceylon.model.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.model.typechecker.model.Value;
/**
* Visits everything, stops at types (aliases, objects, class, interface) and collects
* their Java class name if they are local.
*
* @author Stéphane Épardaud <stef@epardaud.fr>
*/
public class LocalTypeVisitor extends Visitor {
private HasTypeVisitor hasTypeVisitor = new HasTypeVisitor();
private Set<String> locals = new HashSet<String>();
private Set<String> ignored = new HashSet<String>();
private Set<String> localCompanionClasses = new HashSet<String>();
private Set<Interface> localInterfaces = new HashSet<Interface>();
private LinkedList<Integer> anonymous = new LinkedList<Integer>();
{
anonymous.add(1);
}
private String prefix = "";
public Set<String> getLocals() {
locals.removeAll(ignored);
return locals;
}
public Set<Interface> getLocalInterfaces() {
return localInterfaces;
}
private void collect(Node that, Declaration model) {
if(model != null && (!model.isMember()
|| Decl.isObjectExpressionType(model))){
String name = Naming.escapeClassName(model.getName());
Set<String> locals = this.locals;
// FIXME: better name processing
if(model instanceof Value
&& !model.isToplevel())
name = Naming.suffixName(Naming.Suffix.$getter$, name);
if(model instanceof TypedDeclaration || model.isAnonymous())
name += "_";
else if(model instanceof Interface){
// for interfaces we point to the toplevel interface prefixed with ::
// no need to find the local java class
localInterfaces.add((Interface) model);
name = Naming.suffixName(Naming.Suffix.$impl, name);
locals = localCompanionClasses;
}
// find an unused name
int i;
String prefixedName;
for(i=1;locals.contains(prefixedName = prefix+i+name);i++){}
// add it
locals.add(prefixedName);
// only keep it if it contains locals
if(model instanceof TypedDeclaration && !hasTypeVisitor.hasType(that))
ignored.add(prefixedName);
}
}
@Override
public void visit(Tree.AnyMethod that){
Function model = that.getDeclarationModel();
int mpl = model.getParameterLists().size();
String prefix = null;
if(mpl > 1){
prefix = this.prefix;
for(int i=1;i<mpl;i++)
enterAnonymousClass();
}
collect(that, model);
// stop at locals, who get a type generated for them
if(model.isMember())
super.visit(that);
if(mpl > 1){
for(int i=1;i<mpl;i++)
exitAnonymousClass();
this.prefix = prefix;
}
}
@Override
public void visit(Tree.FunctionalParameterDeclaration that) {
Tree.TypedDeclaration typedDeclaration = that.getTypedDeclaration();
boolean anon = typedDeclaration instanceof Tree.MethodDeclaration
&& ((Tree.MethodDeclaration) typedDeclaration).getSpecifierExpression() instanceof Tree.LazySpecifierExpression;
String prefix = this.prefix;
if(anon)
enterAnonymousClass();
super.visit(that);
if(anon)
exitAnonymousClass();
this.prefix = prefix;
}
private void exitAnonymousClass() {
anonymous.pop();
Integer count = anonymous.peek();
anonymous.set(0, count+1);
}
private void enterAnonymousClass() {
anonymous.push(1);
prefix = getPrefix();
}
private String getPrefix() {
StringBuilder b = new StringBuilder();
// ignore the first one and traverse last to second
for(int i=anonymous.size()-1;i>0;i--){
Integer c = anonymous.get(i);
b.append(c).append("$");
}
return b.toString();
}
@Override
public void visit(Tree.Comprehension that) {
String prefix = this.prefix;
// one anonymous for Iterable, one for Iterator
enterAnonymousClass();
enterAnonymousClass();
super.visit(that);
exitAnonymousClass();
exitAnonymousClass();
this.prefix = prefix;
}
@Override
public void visit(Tree.FunctionArgument that) {
String prefix = this.prefix;
enterAnonymousClass();
super.visit(that);
exitAnonymousClass();
this.prefix = prefix;
}
@Override
public void visit(Tree.MethodArgument that) {
String prefix = this.prefix;
enterAnonymousClass();
super.visit(that);
exitAnonymousClass();
this.prefix = prefix;
}
@Override
public void visit(Tree.QualifiedMemberExpression that) {
if(that.getMemberOperator() instanceof Tree.SpreadOp){
String prefix = this.prefix;
enterAnonymousClass();
super.visit(that);
exitAnonymousClass();
this.prefix = prefix;
}else{
super.visit(that);
}
}
@Override
public void visit(Tree.BaseMemberOrTypeExpression that) {
Declaration model = that.getDeclaration();
if(model != null
&& (model instanceof Function || model instanceof Class)
&& !model.isParameter() // if it's a parameter we don't need to wrap it in a class
&& !that.getDirectlyInvoked()){
String prefix = this.prefix;
enterAnonymousClass();
super.visit(that);
exitAnonymousClass();
this.prefix = prefix;
}else{
super.visit(that);
}
}
@Override
public void visit(Tree.SequenceEnumeration that) {
if(that.getSequencedArgument() == null){
// empty literals do not generate any class
super.visit(that);
}else{
String prefix = this.prefix;
enterAnonymousClass();
super.visit(that);
exitAnonymousClass();
this.prefix = prefix;
}
}
@Override
public void visit(Tree.AttributeGetterDefinition that){
Value model = that.getDeclarationModel();
collect(that, model);
// stop at locals, who get a type generated for them
if(model.isMember())
super.visit(that);
}
@Override
public void visit(Tree.AttributeSetterDefinition that){
Setter model = that.getDeclarationModel();
// do not collect setters, they are always referenced by the getter
// stop at locals, who get a type generated for them
if(model.isMember())
super.visit(that);
}
@Override
public void visit(Tree.TypeAliasDeclaration that){
// stop at aliases, do not collect them since we can never create any instance of them
// and they are useless at runtime
}
@Override
public void visit(Tree.ClassOrInterface that){
ClassOrInterface model = that.getDeclarationModel();
// stop at aliases, do not collect them since we can never create any instance of them
// and they are useless at runtime
if(!model.isAlias())
collect(that, model);
}
@Override
public void visit(Tree.ObjectDefinition that){
Value model = that.getDeclarationModel();
if(model != null)
collect(that, model.getTypeDeclaration());
}
@Override
public void visit(Tree.ObjectArgument that){
Value model = that.getDeclarationModel();
if(model != null)
collect(that, model.getTypeDeclaration());
}
@Override
public void visit(Tree.ObjectExpression that){
Class model = that.getAnonymousClass();
if(model != null)
collect(that, model);
}
@Override
public void visit(Tree.AttributeArgument that){
// if there's a block we end up generating an @Attribute class so we can stop there
if(that.getBlock() != null){
Value model = that.getDeclarationModel();
if(model != null)
collect(that, model);
}else{
super.visit(that);
}
}
public void startFrom(Node tree) {
int mpl = 0;
String prefix = null;
// make sure we start with the right number of anonymous classes for methods with MPL before we visit the children
if(tree instanceof Tree.AnyMethod){
Function model = ((Tree.AnyMethod)tree).getDeclarationModel();
mpl = model.getParameterLists().size();
if(mpl > 1){
prefix = this.prefix;
for(int i=1;i<mpl;i++)
enterAnonymousClass();
}
}
tree.visitChildren(this);
if(mpl > 1){
for(int i=1;i<mpl;i++)
exitAnonymousClass();
this.prefix = prefix;
}
}
}