/*
* Copyright 2012-2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.jboss.forge.roaster.model.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jface.text.Document;
import org.jboss.forge.roaster.Roaster;
import org.jboss.forge.roaster.model.Field;
import org.jboss.forge.roaster.model.JavaInterface;
import org.jboss.forge.roaster.model.JavaType;
import org.jboss.forge.roaster.model.Method;
import org.jboss.forge.roaster.model.Parameter;
import org.jboss.forge.roaster.model.Property;
import org.jboss.forge.roaster.model.ast.MethodFinderVisitor;
import org.jboss.forge.roaster.model.source.FieldSource;
import org.jboss.forge.roaster.model.source.Import;
import org.jboss.forge.roaster.model.source.InterfaceCapableSource;
import org.jboss.forge.roaster.model.source.JavaClassSource;
import org.jboss.forge.roaster.model.source.JavaSource;
import org.jboss.forge.roaster.model.source.MemberSource;
import org.jboss.forge.roaster.model.source.MethodSource;
import org.jboss.forge.roaster.model.source.ParameterSource;
import org.jboss.forge.roaster.model.source.PropertyHolderSource;
import org.jboss.forge.roaster.model.source.PropertySource;
import org.jboss.forge.roaster.model.util.Assert;
import org.jboss.forge.roaster.model.util.Strings;
import org.jboss.forge.roaster.model.util.Types;
/**
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*
*/
public abstract class AbstractJavaSourceMemberHolder<O extends JavaSource<O> & PropertyHolderSource<O>> extends AbstractJavaSource<O>
implements InterfaceCapableSource<O>, PropertyHolderSource<O>
{
protected AbstractJavaSourceMemberHolder(JavaSource<?> enclosingType, final Document document,
final CompilationUnit unit, BodyDeclaration declaration)
{
super(enclosingType, document, unit, declaration);
}
/*
* Field & Method modifiers
*/
@Override
@SuppressWarnings("unchecked")
public FieldSource<O> addField()
{
FieldSource<O> field = new FieldImpl<O>((O) this);
addField(field);
return field;
}
@Override
@SuppressWarnings("unchecked")
public FieldSource<O> addField(final String declaration)
{
String stub = "public class Stub { " + declaration + " }";
JavaClassSource temp = (JavaClassSource) Roaster.parse(stub);
List<FieldSource<JavaClassSource>> fields = temp.getFields();
FieldSource<O> result = null;
for (FieldSource<JavaClassSource> stubField : fields)
{
Object variableDeclaration = stubField.getInternal();
FieldSource<O> field = new FieldImpl<O>((O) this, variableDeclaration, true);
addField(field);
if (result == null)
{
result = field;
}
}
return result;
}
@SuppressWarnings("unchecked")
private void addField(Field<O> field)
{
List<Object> bodyDeclarations = getBodyDeclaration().bodyDeclarations();
int idx = 0;
for (Object object : bodyDeclarations)
{
if (!(object instanceof FieldDeclaration))
{
break;
}
idx++;
}
bodyDeclarations.add(idx, ((VariableDeclarationFragment) field.getInternal()).getParent());
}
@Override
public List<MemberSource<O, ?>> getMembers()
{
List<MemberSource<O, ?>> result = new ArrayList<MemberSource<O, ?>>();
result.addAll(getFields());
result.addAll(getMethods());
return result;
}
@Override
@SuppressWarnings("unchecked")
public List<FieldSource<O>> getFields()
{
List<FieldSource<O>> result = new ArrayList<FieldSource<O>>();
List<BodyDeclaration> bodyDeclarations = getBodyDeclaration().bodyDeclarations();
for (BodyDeclaration bodyDeclaration : bodyDeclarations)
{
if (bodyDeclaration instanceof FieldDeclaration)
{
FieldDeclaration fieldDeclaration = (FieldDeclaration) bodyDeclaration;
List<VariableDeclarationFragment> fragments = fieldDeclaration.fragments();
for (VariableDeclarationFragment fragment : fragments)
{
result.add(new FieldImpl<O>((O) this, fragment));
}
}
}
return Collections.unmodifiableList(result);
}
@Override
public FieldSource<O> getField(final String name)
{
for (FieldSource<O> field : getFields())
{
if (field.getName().equals(name))
{
return field;
}
}
return null;
}
@Override
public boolean hasField(final String name)
{
for (FieldSource<O> field : getFields())
{
if (field.getName().equals(name))
{
return true;
}
}
return false;
}
@Override
public boolean hasField(final Field<O> field)
{
return getFields().contains(field);
}
@Override
@SuppressWarnings("unchecked")
public O removeField(final Field<O> field)
{
VariableDeclarationFragment fragment = (VariableDeclarationFragment) field.getInternal();
Iterator<Object> declarationsIterator = getBodyDeclaration().bodyDeclarations().iterator();
while (declarationsIterator.hasNext())
{
Object next = declarationsIterator.next();
if (next instanceof FieldDeclaration)
{
FieldDeclaration declaration = (FieldDeclaration) next;
if (declaration.equals(fragment.getParent()))
{
List<VariableDeclarationFragment> fragments = declaration.fragments();
if (fragments.contains(fragment))
{
if (fragments.size() == 1)
{
declarationsIterator.remove();
}
else
{
fragments.remove(fragment);
}
break;
}
}
}
}
return (O) this;
}
@Override
public boolean hasMethod(final Method<O, ?> method)
{
return getMethods().contains(method);
}
@Override
public boolean hasMethodSignature(final String name)
{
return hasMethodSignature(name, new String[] {});
}
@Override
public boolean hasMethodSignature(final String name, final String... paramTypes)
{
return getMethod(name, paramTypes) != null;
}
@Override
public boolean hasMethodSignature(final String name, Class<?>... paramTypes)
{
if (paramTypes == null)
{
paramTypes = new Class<?>[] {};
}
String[] types = new String[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++)
{
types[i] = paramTypes[i].getName();
}
return hasMethodSignature(name, types);
}
@Override
public MethodSource<O> getMethod(final String name)
{
for (MethodSource<O> method : getMethods())
{
if (method.getName().equals(name) && (method.getParameters().size() == 0))
{
return method;
}
}
return null;
}
@Override
public MethodSource<O> getMethod(final String name, final String... paramTypes)
{
for (MethodSource<O> local : getMethods())
{
if (local.getName().equals(name))
{
List<ParameterSource<O>> localParams = local.getParameters();
if (paramTypes != null)
{
if (localParams.isEmpty() || (localParams.size() == paramTypes.length))
{
boolean matches = true;
for (int i = 0; i < localParams.size(); i++)
{
ParameterSource<O> localParam = localParams.get(i);
String type = paramTypes[i];
if (!Types.areEquivalent(localParam.getType().getName(), type))
{
matches = false;
}
}
if (matches)
return local;
}
}
}
}
return null;
}
@Override
public MethodSource<O> getMethod(final String name, Class<?>... paramTypes)
{
if (paramTypes == null)
{
paramTypes = new Class<?>[] {};
}
String[] types = new String[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++)
{
types[i] = paramTypes[i].getName();
}
return getMethod(name, types);
}
@Override
public boolean hasMethodSignature(final Method<?, ?> method)
{
for (MethodSource<O> local : getMethods())
{
if (local.getName().equals(method.getName()))
{
Iterator<ParameterSource<O>> localParams = local.getParameters().iterator();
for (Parameter<? extends JavaType<?>> methodParam : method.getParameters())
{
if (localParams.hasNext()
&& Strings.areEqual(localParams.next().getType().getName(), methodParam.getType().getName()))
{
continue;
}
return false;
}
return !localParams.hasNext();
}
}
return false;
}
@Override
@SuppressWarnings("unchecked")
public O removeMethod(final Method<O, ?> method)
{
getBodyDeclaration().bodyDeclarations().remove(method.getInternal());
return (O) this;
}
@Override
@SuppressWarnings("unchecked")
public MethodSource<O> addMethod()
{
MethodSource<O> m = new MethodImpl<O>((O) this);
getBodyDeclaration().bodyDeclarations().add(m.getInternal());
return m;
}
@Override
@SuppressWarnings("unchecked")
public MethodSource<O> addMethod(final String method)
{
MethodSource<O> m = new MethodImpl<O>((O) this, method);
getBodyDeclaration().bodyDeclarations().add(m.getInternal());
return m;
}
@Override
@SuppressWarnings("unchecked")
public List<MethodSource<O>> getMethods()
{
List<MethodSource<O>> result = new ArrayList<MethodSource<O>>();
MethodFinderVisitor methodFinderVisitor = new MethodFinderVisitor();
body.accept(methodFinderVisitor);
List<MethodDeclaration> methods = methodFinderVisitor.getMethods();
for (MethodDeclaration methodDeclaration : methods)
{
result.add(new MethodImpl<O>((O) this, methodDeclaration));
}
return Collections.unmodifiableList(result);
}
@Override
public List<String> getInterfaces()
{
List<String> result = new ArrayList<String>();
List<Type> superTypes = JDTHelper.getInterfaces(getBodyDeclaration());
for (Type type : superTypes)
{
String name = JDTHelper.getTypeName(type);
if (Types.isSimpleName(name) && this.hasImport(name))
{
Import imprt = this.getImport(name);
String pkg = imprt.getPackage();
if (!Strings.isNullOrEmpty(pkg))
{
name = pkg + "." + name;
}
}
result.add(name);
}
return result;
}
@SuppressWarnings("unchecked")
@Override
public O addInterface(final String type)
{
if (!this.hasInterface(type))
{
Type interfaceType = JDTHelper.getInterfaces(
Roaster.parse(JavaInterfaceImpl.class,
"public interface Mock extends " + Types.toSimpleName(type)
+ " {}").getBodyDeclaration()).get(0);
if (this.hasInterface(Types.toSimpleName(type)) || this.hasImport(Types.toSimpleName(type)))
{
interfaceType = JDTHelper.getInterfaces(Roaster.parse(JavaInterfaceImpl.class,
"public interface Mock extends " + type + " {}").getBodyDeclaration()).get(0);
}
this.addImport(type);
ASTNode node = ASTNode.copySubtree(unit.getAST(), interfaceType);
JDTHelper.getInterfaces(getBodyDeclaration()).add((Type) node);
}
return (O) this;
}
@Override
public O addInterface(final Class<?> type)
{
return addInterface(type.getName());
}
@Override
public O addInterface(final JavaInterface<?> type)
{
return addInterface(type.getQualifiedName());
}
@Override
public boolean hasInterface(final String type)
{
for (String name : getInterfaces())
{
if (Types.areEquivalent(name, type))
{
return true;
}
}
return false;
}
@Override
public boolean hasInterface(final Class<?> type)
{
return hasInterface(type.getName());
}
@Override
public boolean hasInterface(final JavaInterface<?> type)
{
return hasInterface(type.getQualifiedName());
}
@SuppressWarnings("unchecked")
@Override
public O removeInterface(final String type)
{
List<Type> interfaces = JDTHelper.getInterfaces(getBodyDeclaration());
for (Type i : interfaces)
{
if (Types.areEquivalent(i.toString(), type))
{
interfaces.remove(i);
break;
}
}
return (O) this;
}
@Override
public O removeInterface(final Class<?> type)
{
return removeInterface(type.getName());
}
@Override
public O removeInterface(final JavaInterface<?> type)
{
return removeInterface(type.getQualifiedName());
}
@Override
public final boolean hasProperty(String name)
{
return getProperty(name) != null;
}
@Override
public final boolean hasProperty(Property<O> property)
{
return getProperties().contains(property);
}
@Override
public final PropertySource<O> addProperty(String type, String name)
{
Assert.isFalse(hasProperty(name), "Cannot create existing property " + name);
final org.jboss.forge.roaster.model.Type<O> typeObject = new TypeImpl<O>(getOrigin(), null, type);
final PropertySource<O> result = new PropertyImpl<O>(name, getOrigin())
{
@Override
public org.jboss.forge.roaster.model.Type<O> getType()
{
final org.jboss.forge.roaster.model.Type<O> result = super.getType();
return result == null ? typeObject : result;
}
};
if (!isInterface())
{
result.createField();
}
result.setAccessible(true);
result.setMutable(!isEnum());
return getProperty(name);
}
@Override
public PropertySource<O> addProperty(Class<?> type, String name)
{
return addProperty(type.getName(), name);
}
@Override
public PropertySource<O> addProperty(JavaType<?> type, String name)
{
return addProperty(type.getQualifiedName(), name);
}
@Override
public final AbstractJavaSourceMemberHolder<O> removeProperty(Property<O> property)
{
if (hasProperty(property))
{
getProperty(property.getName()).setMutable(false).setAccessible(false).removeField();
}
return this;
}
@Override
public final PropertySource<O> getProperty(String name)
{
Assert.notNull(name, "name is null");
final PropertyImpl<O> result = new PropertyImpl<O>(name, getOrigin());
return result.isValid() ? result : null;
}
@Override
public final List<PropertySource<O>> getProperties()
{
final Set<String> propertyNames = new LinkedHashSet<String>();
for (MethodSource<O> method : getMethods())
{
if (isAccessor(method) || isMutator(method))
{
propertyNames.add(extractPropertyName(method));
}
}
for (FieldSource<O> field : getFields())
{
if (!field.isStatic())
{
propertyNames.add(field.getName());
}
}
final List<PropertySource<O>> result = new ArrayList<PropertySource<O>>(propertyNames.size());
for (String name : propertyNames)
{
result.add(new PropertyImpl<O>(name, getOrigin()));
}
return result;
}
private boolean isAccessor(Method<O, ?> method)
{
return extractPropertyName(method) != null && method.getParameters().isEmpty() && !method.isReturnTypeVoid();
}
private boolean isMutator(Method<O, ?> method)
{
return extractPropertyName(method) != null && method.getParameters().size() == 1 && method.isReturnTypeVoid();
}
private String extractPropertyName(Method<O, ?> method)
{
if (method.getName().matches("^[gs]et.+$"))
{
return Strings.uncapitalize(method.getName().substring(3));
}
return null;
}
}