/**
 * Copyright (C) 2010 STMicroelectronics
 *
 * This file is part of "Mind Compiler" is free software: you can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public License 
 * as published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact: mind@ow2.org
 *
 * Authors: Matthieu Leclercq
 * Contributors: 
 */

package org.ow2.mind.idl;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.objectweb.fractal.adl.ADLException;
import org.objectweb.fractal.adl.Node;
import org.ow2.mind.idl.ast.ArrayOf;
import org.ow2.mind.idl.ast.ConstantDefinition;
import org.ow2.mind.idl.ast.EnumDefinition;
import org.ow2.mind.idl.ast.EnumMember;
import org.ow2.mind.idl.ast.EnumReference;
import org.ow2.mind.idl.ast.IDL;
import org.ow2.mind.idl.ast.IDLASTHelper;
import org.ow2.mind.idl.ast.Include;
import org.ow2.mind.idl.ast.IncludeContainer;
import org.ow2.mind.idl.ast.InterfaceDefinition;
import org.ow2.mind.idl.ast.Member;
import org.ow2.mind.idl.ast.MemberContainer;
import org.ow2.mind.idl.ast.Method;
import org.ow2.mind.idl.ast.Parameter;
import org.ow2.mind.idl.ast.PointerOf;
import org.ow2.mind.idl.ast.PrimitiveType;
import org.ow2.mind.idl.ast.StructDefinition;
import org.ow2.mind.idl.ast.StructReference;
import org.ow2.mind.idl.ast.Type;
import org.ow2.mind.idl.ast.TypeCollectionContainer;
import org.ow2.mind.idl.ast.TypeDefReference;
import org.ow2.mind.idl.ast.TypeDefinition;
import org.ow2.mind.idl.ast.UnionDefinition;
import org.ow2.mind.idl.ast.UnionReference;

public class ASTChecker {

  protected final Map<Node, Object> checkers = new IdentityHashMap<Node, Object>();

  // ---------------------------------------------------------------------------
  // IDL checking
  // ---------------------------------------------------------------------------

  protected void checkIDL(final IDL idl) {
    assertNotNull("IDL is null", idl);
    assertNotNull("idl name is null", idl.getName());

    if (idl instanceof IncludeContainer)
      checkIncludeContainer((IncludeContainer) idl);

    if (idl instanceof TypeCollectionContainer) {
      for (final Type type : ((TypeCollectionContainer) idl).getTypes()) {
        checkType(type);
      }
    }

    if (idl instanceof InterfaceDefinition)
      checkInterface((InterfaceDefinition) idl);
  }

  protected void checkInterface(final InterfaceDefinition itf) {
    for (final Method method : itf.getMethods()) {
      checkMethod(method);
    }
  }

  public IDLChecker assertIDL(final IDL idl) {
    IDLChecker checker = (IDLChecker) checkers.get(idl);
    if (checker == null) {
      checker = createIDLChecker(idl);
      checkers.put(idl, checker);
    }
    return checker;
  }

  protected IDLChecker createIDLChecker(final IDL idl) {
    return new IDLChecker(idl);
  }

  public class IDLChecker {
    public final IDL idl;

    public IDLChecker(final IDL idl) {
      this.idl = idl;
      checkIDL(idl);
    }

    public IDLChecker that() {
      return this;
    }

    public IDLChecker and() {
      return this;
    }

    public IDLChecker extendsInterface(final String name) {
      if (!(idl instanceof InterfaceDefinition))
        fail("idl do not implements InterfaceDefinition");

      final InterfaceDefinition container = (InterfaceDefinition) idl;
      assertEquals("IDL " + idl.getName() + " does not extends \"" + name
          + "\".", name, container.getExtends());
      return this;
    }

    public MethodChecker containsMethod(final String name) {
      if (!(idl instanceof InterfaceDefinition))
        fail("idl do not implements InterfaceDefinition");

      final InterfaceDefinition container = (InterfaceDefinition) idl;
      for (final Method meth : container.getMethods()) {
        if (name.equals(meth.getName())) return assertMethod(meth, container);
      }
      fail("IDL " + idl.getName() + " does not contains a \"" + name
          + "\" method.");
      // unreachable
      return null;

    }

    public MethodCheckerIterator containsMethods(final String... names) {
      final Set<String> nameSet = new HashSet<String>();
      final List<MethodChecker> list = new ArrayList<MethodChecker>(
          names.length);
      for (final String name : names) {
        assertTrue("Duplucated string in given names " + names, nameSet
            .add(name));
        list.add(containsMethod(name));
      }
      assertEquals("IDL contains more methods than expected.", names.length,
          ((InterfaceDefinition) idl).getMethods().length);
      return new MethodCheckerIterator(list);
    }

    public IncludeChecker containsInclude(final String name) {
      if (!(idl instanceof IncludeContainer))
        fail("idl do not implements IncludeContainer");

      final IncludeContainer container = (IncludeContainer) idl;
      for (final Include include : container.getIncludes()) {
        if (name.equals(include.getPath()))
          return assertInclude(include, container);
      }
      fail("IDL " + idl.getName() + " does not include \"" + name + "\".");
      // unreachable
      return null;
    }

    public IncludeCheckerIterator containsIncludes(final String... names) {
      final Set<String> nameSet = new HashSet<String>();
      final List<IncludeChecker> list = new ArrayList<IncludeChecker>(
          names.length);
      for (final String name : names) {
        assertTrue("Duplucated string in given names " + names, nameSet
            .add(name));
        list.add(containsInclude(name));
      }
      assertEquals("IDL contains more includes than expected.", names.length,
          ((IncludeContainer) idl).getIncludes().length);
      return new IncludeCheckerIterator(list);
    }

    public TypeCheckerIterator definesType() {
      if (!(idl instanceof TypeCollectionContainer))
        fail("idl do not implements TypeCollectionContainer");

      final TypeCollectionContainer container = (TypeCollectionContainer) idl;
      final List<TypeChecker> checkers = new ArrayList<TypeChecker>();
      for (final Type type : container.getTypes()) {
        checkers.add(assertType(type));
      }
      return new TypeCheckerIterator(checkers);
    }
  }

  // ---------------------------------------------------------------------------
  // Include checking
  // ---------------------------------------------------------------------------

  protected void checkInclude(final Include include) {
    assertNotNull("Include node is null", include);
    assertNotNull("Include path is null", include.getPath());
  }

  protected void checkIncludeContainer(final IncludeContainer container) {
    for (final Include include : container.getIncludes()) {
      checkInclude(include);
    }
  }

  protected IncludeChecker assertInclude(final Include include,
      final IncludeContainer container) {
    IncludeChecker checker = (IncludeChecker) checkers.get(include);
    if (checker == null) {
      checker = createIncludeChecker(include, container);
      checkers.put(include, checker);
    } else {
      assertSame("Unexpected container", container, checker.container);
    }
    return checker;
  }

  protected IncludeChecker createIncludeChecker(final Include include,
      final IncludeContainer container) {
    return new IncludeChecker(include, container);
  }

  public class IncludeChecker {
    public final Include          include;
    public final IncludeContainer container;

    protected IncludeChecker(final Include include,
        final IncludeContainer container) {
      this.include = include;
      this.container = container;
      checkInclude(include);
    }

    public IncludeChecker pathIs(final String path) {
      assertEquals("Unexpected include path \"" + include.getPath() + "\".",
          path, include.getPath());
      return this;
    }

    public IDLChecker includes() {
      try {
        return assertIDL(IDLASTHelper.getIncludedIDL(include, null, null));
      } catch (final ADLException e) {
        // never append
        return null;
      }
    }
  }

  public class IncludeCheckerIterator
      extends
        CheckerIterator<IncludeCheckerIterator, IncludeChecker> {

    public IncludeCheckerIterator(final List<IncludeChecker> list) {
      super(list);
    }

    @Override
    protected IncludeCheckerIterator getThis() {
      return this;
    }

    public IncludeCheckerIterator pathIs(final String path) {
      element.pathIs(path);
      return this;
    }

    public IDLChecker includes() {
      return element.includes();
    }
  }

  // ---------------------------------------------------------------------------
  // Method checking
  // ---------------------------------------------------------------------------

  protected void checkMethod(final Method method) {
    assertNotNull("Method node is null", method);
    assertNotNull("Method name is null", method.getName());
    checkType(method.getType());
    final Set<String> names = new HashSet<String>();
    for (final Parameter param : method.getParameters()) {
      assertTrue("Duplicated parameter name.", names.add(param.getName()));
      checkParameter(param);
    }
  }

  protected MethodChecker assertMethod(final Method method,
      final InterfaceDefinition container) {
    MethodChecker checker = (MethodChecker) checkers.get(method);
    if (checker == null) {
      checker = createMethodChecker(method, container);
      checkers.put(method, checker);
    } else {
      assertSame("Unexpected container", container, checker.container);
    }
    return checker;
  }

  protected MethodChecker createMethodChecker(final Method method,
      final InterfaceDefinition container) {
    return new MethodChecker(method, container);
  }

  public class MethodChecker {
    public final Method              method;
    public final InterfaceDefinition container;

    protected MethodChecker(final Method method,
        final InterfaceDefinition container) {
      this.method = method;
      this.container = container;
      checkMethod(method);
    }

    public TypeChecker returnsType() {
      return assertType(method.getType());
    }

    public ParameterChecker hasParameter(final String name) {
      for (final Parameter param : method.getParameters()) {
        if (name.equals(param.getName())) {
          return assertParameter(param, method);
        }
      }
      fail("method \"" + method.getName() + "\"does not contains a \"" + name
          + "\" parameter.");
      // unreachable
      return null;
    }

    public ParameterCheckerIterator hasParameters(final String... names) {
      final Set<String> nameSet = new HashSet<String>();
      final List<ParameterChecker> list = new ArrayList<ParameterChecker>(
          names.length);
      for (final String name : names) {
        assertTrue("Duplucated string in given names " + names, nameSet
            .add(name));
        list.add(hasParameter(name));
      }
      assertEquals("Method contains more parameter than expected.",
          names.length, method.getParameters().length);

      return new ParameterCheckerIterator(list);
    }

  }

  public class MethodCheckerIterator
      extends
        CheckerIterator<MethodCheckerIterator, MethodChecker> {

    public MethodCheckerIterator(final List<MethodChecker> list) {
      super(list);
    }

    @Override
    protected MethodCheckerIterator getThis() {
      return this;
    }

    public TypeChecker returnsType() {
      return element.returnsType();
    }

    public ParameterChecker hasParameter(final String name) {
      return element.hasParameter(name);
    }

    public ParameterCheckerIterator hasParameters(final String... names) {
      return element.hasParameters(names);
    }
  }

  // ---------------------------------------------------------------------------
  // Parameter checking
  // ---------------------------------------------------------------------------

  protected void checkParameter(final Parameter parameter) {
    assertNotNull("Parameter node is null", parameter);
    assertNotNull("Parameter name is null", parameter.getName());
    checkType(parameter.getType());
  }

  protected ParameterChecker assertParameter(final Parameter parameter,
      final Method container) {
    ParameterChecker checker = (ParameterChecker) checkers.get(parameter);
    if (checker == null) {
      checker = createParameterChecker(parameter, container);
      checkers.put(parameter, checker);
    } else {
      assertSame("Unexpected container", container, checker.container);
    }
    return checker;
  }

  protected ParameterChecker createParameterChecker(final Parameter parameter,
      final Method container) {
    return new ParameterChecker(parameter, container);
  }

  public class ParameterChecker {
    public final Parameter parameter;
    public final Method    container;

    protected ParameterChecker(final Parameter parameter, final Method container) {
      this.parameter = parameter;
      this.container = container;
      checkParameter(parameter);
    }

    public TypeChecker hasType() {
      return assertType(parameter.getType());
    }

  }

  public class ParameterCheckerIterator
      extends
        CheckerIterator<ParameterCheckerIterator, ParameterChecker> {

    public ParameterCheckerIterator(final List<ParameterChecker> list) {
      super(list);
    }

    @Override
    protected ParameterCheckerIterator getThis() {
      return this;
    }

    public TypeChecker hasType() {
      return element.hasType();
    }
  }

  // ---------------------------------------------------------------------------
  // Type checking
  // ---------------------------------------------------------------------------

  protected void checkType(final Type type) {
    assertNotNull("Type node is null", type);
    if (type instanceof EnumDefinition) {
      checkType((EnumDefinition) type);
    } else if (type instanceof EnumReference) {
      checkType((EnumReference) type);
    } else if (type instanceof StructDefinition) {
      checkType((StructDefinition) type);
    } else if (type instanceof StructReference) {
      checkType((StructReference) type);
    } else if (type instanceof UnionDefinition) {
      checkType((UnionDefinition) type);
    } else if (type instanceof UnionReference) {
      checkType((UnionReference) type);
    } else if (type instanceof TypeDefinition) {
      checkType((TypeDefinition) type);
    } else if (type instanceof TypeDefReference) {
      checkType((TypeDefReference) type);
    } else if (type instanceof ConstantDefinition) {
      checkType((ConstantDefinition) type);
    } else if (type instanceof PrimitiveType) {
      checkType((PrimitiveType) type);
    } else if (type instanceof ArrayOf) {
      checkType((ArrayOf) type);
    } else if (type instanceof PointerOf) {
      checkType((PointerOf) type);
    }
  }

  protected void checkType(final EnumDefinition type) {
    final Set<String> names = new HashSet<String>();
    for (final EnumMember member : type.getEnumMembers()) {
      assertTrue("Duplicated member name.", names.add(member.getName()));
      checkEnumMember(member);
    }
  }

  protected void checkType(final EnumReference type) {
    assertNotNull("Type name is null", type.getName());
  }

  protected void checkType(final StructDefinition type) {
    final Set<String> names = new HashSet<String>();
    for (final Member member : type.getMembers()) {
      assertTrue("Duplicated member name.", names.add(member.getName()));
      checkMember(member);
    }
  }

  protected void checkType(final StructReference type) {
    assertNotNull("Type name is null", type.getName());
  }

  protected void checkType(final UnionDefinition type) {
    final Set<String> names = new HashSet<String>();
    for (final Member member : type.getMembers()) {
      assertTrue("Duplicated member name.", names.add(member.getName()));
      checkMember(member);
    }
  }

  protected void checkType(final UnionReference type) {
    assertNotNull("Type name is null", type.getName());
  }

  protected void checkType(final TypeDefinition type) {
    assertNotNull("Type name is null", type.getName());
    checkType(type.getType());
  }

  protected void checkType(final TypeDefReference type) {
    assertNotNull("Type name is null", type.getName());
  }

  protected void checkType(final ConstantDefinition type) {
    assertNotNull("Type name is null", type.getName());
  }

  protected void checkType(final PrimitiveType type) {
    assertNotNull("Type name is null", type.getName());
  }

  protected void checkType(final ArrayOf type) {
    checkType(type.getType());
  }

  protected void checkType(final PointerOf type) {
    checkType(type.getType());
  }

  protected TypeChecker assertType(final Type type) {
    TypeChecker checker = (TypeChecker) checkers.get(type);
    if (checker == null) {
      checker = createTypeChecker(type);
      checkers.put(type, checker);
    }
    return checker;
  }

  protected TypeChecker createTypeChecker(final Type type) {
    return new TypeChecker(type);
  }

  public class TypeChecker {
    public final Type type;

    protected TypeChecker(final Type type) {
      this.type = type;
      checkType(type);
    }

    public TypeChecker isPrimitiveType(final String name) {
      assertTrue("Type node is not a PrimitiveType",
          type instanceof PrimitiveType);
      assertEquals(name, ((PrimitiveType) type).getName());
      return this;
    }

    public TypeChecker isPointerOf() {
      assertTrue("Type node is not a PointerOf", type instanceof PointerOf);
      return assertType(((PointerOf) type).getType());
    }

    public TypeChecker isArrayOf() {
      assertTrue("Type node is not a ArrayOf", type instanceof ArrayOf);
      return assertType(((ArrayOf) type).getType());
    }

    public TypeChecker isTypedefOf(final String name) {
      assertTrue("Type node is not a TypeDefinition",
          type instanceof TypeDefinition);
      assertEquals(name, ((TypeDefinition) type).getName());
      return assertType(((TypeDefinition) type).getType());
    }

    public void isTypedefRefOf(final String name) {
      assertTrue("Type node is not a TypeDefReference",
          type instanceof TypeDefReference);
      assertEquals(name, ((TypeDefReference) type).getName());
    }

    public TypeChecker isEnumRef(final String name) {
      assertTrue("Type node is not a EnumReference",
          type instanceof EnumReference);
      assertEquals(name, ((EnumReference) type).getName());
      return this;
    }

    public TypeChecker isStructRef(final String name) {
      assertTrue("Type node is not a StructReference",
          type instanceof StructReference);
      assertEquals(name, ((StructReference) type).getName());
      return this;
    }

    public TypeChecker isUnionRef(final String name) {
      assertTrue("Type node is not a UnionReference",
          type instanceof UnionReference);
      assertEquals(name, ((UnionReference) type).getName());
      return this;
    }

    public EnumChecker isEnumDef(final String name) {
      assertTrue("Type node is not a EnumDefinition",
          type instanceof EnumDefinition);
      assertEquals(name, ((EnumDefinition) type).getName());
      return assertEnum((EnumDefinition) type);
    }

    public StructUnionChecker isStructDef(final String name) {
      assertTrue("Type node is not a StructDefinition",
          type instanceof StructDefinition);
      assertEquals(name, ((StructDefinition) type).getName());
      return assertStructUnion((StructDefinition) type);
    }

    public StructUnionChecker isUnionDef(final String name) {
      assertTrue("Type node is not a UnionDefinition",
          type instanceof UnionDefinition);
      assertEquals(name, ((UnionDefinition) type).getName());
      return assertStructUnion((UnionDefinition) type);
    }
  }

  public class TypeCheckerIterator
      extends
        CheckerIterator<TypeCheckerIterator, TypeChecker> {

    public TypeCheckerIterator(final List<TypeChecker> list) {
      super(list);
    }

    @Override
    protected TypeCheckerIterator getThis() {
      return this;
    }

    public TypeChecker isPrimitiveType(final String name) {
      return element.isPrimitiveType(name);
    }

    public TypeChecker isPointerOf() {
      return element.isPointerOf();
    }

    public TypeChecker isArrayOf() {
      return element.isArrayOf();
    }

    public TypeChecker isTypedefOf(final String name) {
      return element.isTypedefOf(name);
    }

    public TypeCheckerIterator isTypedefRefOf(final String name) {
      element.isTypedefRefOf(name);
      return this;
    }

    public TypeChecker isEnumRef(final String name) {
      return element.isEnumRef(name);
    }

    public TypeChecker isStructRef(final String name) {
      return element.isStructRef(name);
    }

    public TypeChecker isUnionRef(final String name) {
      return element.isUnionRef(name);
    }

    public EnumChecker isEnumDef(final String name) {
      return element.isEnumDef(name);
    }

    public StructUnionChecker isStructDef(final String name) {
      return element.isStructDef(name);
    }

    public StructUnionChecker isUnionDef(final String name) {
      return element.isUnionDef(name);
    }
  }

  public EnumChecker assertEnum(final EnumDefinition enumDef) {
    return createEnumChecker(enumDef);
  }

  protected EnumChecker createEnumChecker(final EnumDefinition enumDef) {
    return new EnumChecker(enumDef);
  }

  public class EnumChecker {
    EnumDefinition enumDef;

    EnumChecker(final EnumDefinition enumDef) {
      this.enumDef = enumDef;
      checkType(enumDef);
    }

    public EnumMemberChecker hasMember(final String name) {
      for (final EnumMember member : enumDef.getEnumMembers()) {
        if (name.equals(member.getName())) {
          return assertEnumMember(member);
        }
      }
      fail("Enum does not contains a \"" + name + "\" member.");
      // unreachable
      return null;
    }

    public EnumMemberCheckerIterator hasMembers(final String... names) {
      final Set<String> nameSet = new HashSet<String>();
      final List<EnumMemberChecker> list = new ArrayList<EnumMemberChecker>(
          names.length);
      for (final String name : names) {
        assertTrue("Duplucated string in given names " + names, nameSet
            .add(name));
        list.add(hasMember(name));
      }
      assertEquals("Enum contains more members than expected.", names.length,
          enumDef.getEnumMembers().length);

      return new EnumMemberCheckerIterator(list);
    }
  }

  protected void checkEnumMember(final EnumMember member) {
    assertNotNull("EnumMember is null", member);
    assertNotNull("EnumMember name is null", member.getName());
  }

  public EnumMemberChecker assertEnumMember(final EnumMember member) {
    EnumMemberChecker checker = (EnumMemberChecker) checkers.get(member);
    if (checker == null) {
      checker = createEnumMemberChecker(member);
      checkers.put(member, checker);
    }
    return checker;
  }

  protected EnumMemberChecker createEnumMemberChecker(final EnumMember member) {
    return new EnumMemberChecker(member);
  }

  public class EnumMemberChecker {
    public final EnumMember member;

    public EnumMemberChecker(final EnumMember member) {
      this.member = member;
      checkEnumMember(member);
    }

    public EnumMemberChecker hasValue() {
      assertNotNull(member.getConstantExpression());
      return this;
    }
  }

  public class EnumMemberCheckerIterator
      extends
        CheckerIterator<EnumMemberCheckerIterator, EnumMemberChecker> {

    public EnumMemberCheckerIterator(final List<EnumMemberChecker> list) {
      super(list);
    }

    @Override
    protected EnumMemberCheckerIterator getThis() {
      return this;
    }

    public EnumMemberCheckerIterator hasValue() {
      element.hasValue();
      return this;
    }
  }

  public StructUnionChecker assertStructUnion(final MemberContainer structUnion) {
    return createStructUnionChecker(structUnion);
  }

  protected StructUnionChecker createStructUnionChecker(
      final MemberContainer structUnion) {
    return new StructUnionChecker(structUnion);
  }

  public class StructUnionChecker {
    MemberContainer structUnion;

    StructUnionChecker(final MemberContainer structUnion) {
      this.structUnion = structUnion;
      checkType((Type) structUnion);
    }

    public StructUnionMemberChecker hasMember(final String name) {
      for (final Member member : structUnion.getMembers()) {
        if (name.equals(member.getName())) {
          return assertStructUnionMember(member);
        }
      }
      fail("Enum does not contains a \"" + name + "\" member.");
      // unreachable
      return null;
    }

    public StructUnionMemberCheckerIterator hasMembers(final String... names) {
      final Set<String> nameSet = new HashSet<String>();
      final List<StructUnionMemberChecker> list = new ArrayList<StructUnionMemberChecker>(
          names.length);
      for (final String name : names) {
        assertTrue("Duplucated string in given names " + names, nameSet
            .add(name));
        list.add(hasMember(name));
      }
      assertEquals("Struct/Union contains more members than expected.",
          names.length, structUnion.getMembers().length);

      return new StructUnionMemberCheckerIterator(list);
    }
  }

  protected void checkMember(final Member member) {
    assertNotNull("Member is null", member);
    assertNotNull("Member name is null", member.getName());
    checkType(member.getType());
  }

  public StructUnionMemberChecker assertStructUnionMember(final Member member) {
    StructUnionMemberChecker checker = (StructUnionMemberChecker) checkers
        .get(member);
    if (checker == null) {
      checker = createStructUnionMemberChecker(member);
      checkers.put(member, checker);
    }
    return checker;
  }

  protected StructUnionMemberChecker createStructUnionMemberChecker(
      final Member member) {
    return new StructUnionMemberChecker(member);
  }

  public class StructUnionMemberChecker {
    public final Member member;

    public StructUnionMemberChecker(final Member member) {
      this.member = member;
      checkMember(member);
    }

    public StructUnionMemberChecker hasSize() {
      assertNotNull(member.getConstantExpression());
      return this;
    }

    public TypeChecker hasType() {
      return assertType(member.getType());
    }
  }

  public class StructUnionMemberCheckerIterator
      extends
        CheckerIterator<StructUnionMemberCheckerIterator, StructUnionMemberChecker> {

    public StructUnionMemberCheckerIterator(
        final List<StructUnionMemberChecker> list) {
      super(list);
    }

    @Override
    protected StructUnionMemberCheckerIterator getThis() {
      return this;
    }

    public StructUnionMemberCheckerIterator hasSize() {
      element.hasSize();
      return this;
    }

    public TypeChecker hasType() {
      return element.hasType();
    }
  }

  // ---------------------------------------------------------------------------
  // Utility
  // ---------------------------------------------------------------------------

  public static abstract class CheckerIterator<T extends CheckerIterator<?, ?>, U> {
    protected final List<U> list;
    protected Iterator<U>   iterator;
    protected U             element;

    public CheckerIterator(final List<U> list) {
      this.list = list;
    }

    protected abstract T getThis();

    public T whereFirst() {
      iterator = list.iterator();
      if (!iterator.hasNext()) fail("List is empty");
      element = iterator.next();
      return getThis();
    }

    public T andNext() {
      if (!iterator.hasNext()) fail("List has no more element");
      element = iterator.next();
      return getThis();
    }

    public U that() {
      return element;
    }
  }
}
