コード例 #1
0
ファイル: GenUtil.java プロジェクト: SemenChernyy/errai
  public static MetaMethod findCaseInsensitiveMatch(
      final MetaClass retType, final MetaClass clazz, final String name, final MetaClass... parms) {
    MetaClass c = clazz;

    do {
      Outer:
      for (final MetaMethod method : c.getDeclaredMethods()) {
        if (name.equalsIgnoreCase(method.getName())) {
          if (parms.length != method.getParameters().length) continue;

          final MetaParameter[] mps = method.getParameters();
          for (int i = 0; i < parms.length; i++) {
            if (!parms[i]
                .getFullyQualifiedName()
                .equals(mps[i].getType().getFullyQualifiedName())) {
              continue Outer;
            }
          }

          if (retType != null
              && !retType
                  .getFullyQualifiedName()
                  .equals(method.getReturnType().getFullyQualifiedName())) {
            continue;
          }

          return method;
        }
      }
    } while ((c = c.getSuperClass()) != null);

    return null;
  }
コード例 #2
0
  /**
   * Ensures the provided type is a {@link DataBinder} and throws a {@link GenerationException} in
   * case it's not.
   *
   * @param type the type to check
   */
  private static void assertTypeIsDataBinder(MetaClass type) {
    final MetaClass databinderMetaClass = MetaClassFactory.get(DataBinder.class);

    if (!databinderMetaClass.isAssignableFrom(type)) {
      throw new GenerationException(
          "Type of @AutoBound element must be "
              + DataBinder.class.getName()
              + " but is: "
              + type.getFullyQualifiedName());
    }
  }
コード例 #3
0
 private void createValidationError(Collection<MetaClass> pages, Class<?> role) {
   StringBuilder builder = new StringBuilder();
   for (MetaClass mc : pages) {
     builder.append("\n  ").append(mc.getFullyQualifiedName());
   }
   throw new GenerationException(
       "Found more than one @Page with role = '"
           + role
           + "': "
           + builder
           + "\nExactly one @Page class must be designated with this unique role.");
 }
コード例 #4
0
ファイル: GenUtil.java プロジェクト: SemenChernyy/errai
  public static void assertAssignableTypes(
      final Context context, final MetaClass from, final MetaClass to) {
    if (!to.asBoxed().isAssignableFrom(from.asBoxed())) {
      if (to.isArray()
          && from.isArray()
          && GenUtil.getArrayDimensions(to) == GenUtil.getArrayDimensions(from)
          && to.getOuterComponentType().isAssignableFrom(from.getOuterComponentType())) {
        return;
      }

      if (!context.isPermissiveMode()) {
        if (classAliases.contains(from.getFullyQualifiedName())
            && classAliases.contains(to.getFullyQualifiedName())) {
          // handle convertibility between MetaClass API and java Class reference.
          return;
        }

        throw new InvalidTypeException(
            to.getFullyQualifiedName() + " is not assignable from " + from.getFullyQualifiedName());
      }
    }
  }
コード例 #5
0
 private Statement defaultValueStatement(MetaClass type) {
   if (type.isPrimitive()) {
     if (type.asBoxed().isAssignableTo(Number.class)) {
       return Stmt.castTo(type, Stmt.load(0));
     } else if (type.isAssignableTo(boolean.class)) {
       return Stmt.load(false);
     } else {
       throw new UnsupportedOperationException(
           "Don't know how to make a default value for @PageState field of type "
               + type.getFullyQualifiedName());
     }
   } else {
     return Stmt.load(null);
   }
 }
コード例 #6
0
ファイル: GenUtil.java プロジェクト: SemenChernyy/errai
 public static boolean isPrimitiveWrapper(final MetaClass clazz) {
   return Integer.class.getName().equals(clazz.getFullyQualifiedName())
       || Boolean.class.getName().equals(clazz.getFullyQualifiedName())
       || Long.class.getName().equals(clazz.getFullyQualifiedName())
       || Double.class.getName().equals(clazz.getFullyQualifiedName())
       || Float.class.getName().equals(clazz.getFullyQualifiedName())
       || Short.class.getName().equals(clazz.getFullyQualifiedName())
       || Character.class.getName().equals(clazz.getFullyQualifiedName())
       || Byte.class.getName().equals(clazz.getFullyQualifiedName());
 }
コード例 #7
0
ファイル: GenUtil.java プロジェクト: SemenChernyy/errai
  public static Statement convert(final Context context, Object input, final MetaClass targetType) {
    try {
      if (input instanceof Statement) {
        if (input instanceof LiteralValue<?>) {
          input = ((LiteralValue<?>) input).getValue();
        } else {
          if ("null".equals(((Statement) input).generate(context))) {
            return (Statement) input;
          }

          assertAssignableTypes(context, ((Statement) input).getType(), targetType);
          return (Statement) input;
        }
      }

      if (input != null
          && MetaClassFactory.get(input.getClass())
              .getOuterComponentType()
              .getFullyQualifiedName()
              .equals(MetaClass.class.getName())) {
        return generate(context, input);
      }

      if (Object.class.getName().equals(targetType.getFullyQualifiedName())) {
        return generate(context, input);
      }

      Class<?> inputClass = input == null ? Object.class : input.getClass();

      if (MetaClass.class.isAssignableFrom(inputClass)) {
        inputClass = Class.class;
      }

      final Class<?> targetClass = targetType.asBoxed().asClass();
      if (NullType.class.getName().equals(targetClass.getName())) {
        return generate(context, input);
      }

      if (!targetClass.isAssignableFrom(inputClass)
          && DataConversion.canConvert(targetClass, inputClass)) {
        return generate(context, DataConversion.convert(input, targetClass));
      } else {
        return generate(context, input);
      }
    } catch (NumberFormatException nfe) {
      throw new InvalidTypeException(nfe);
    }
  }
コード例 #8
0
  private static Statement paramFromStringStatement(MetaClass toType, Statement stringValue) {

    // make sure it's really a string
    stringValue = Stmt.castTo(String.class, stringValue);

    if (toType.isAssignableTo(String.class)) {
      return stringValue;
    } else if (toType.asBoxed().isAssignableTo(Number.class)) {
      return Stmt.invokeStatic(toType.asBoxed(), "valueOf", stringValue);
    } else if (toType.asBoxed().isAssignableTo(Boolean.class)) {
      return Stmt.invokeStatic(Boolean.class, "valueOf", stringValue);
    } else {
      throw new UnsupportedOperationException(
          "@PageState fields of type " + toType.getFullyQualifiedName() + " are not supported");
    }
  }
コード例 #9
0
ファイル: GenUtil.java プロジェクト: SemenChernyy/errai
 public static MetaClass getUnboxedFromWrapper(final MetaClass clazz) {
   if (Integer.class.getName().equals(clazz.getFullyQualifiedName())) {
     return MetaClassFactory.get(int.class);
   } else if (Boolean.class.getName().equals(clazz.getFullyQualifiedName())) {
     return MetaClassFactory.get(boolean.class);
   } else if (Long.class.getName().equals(clazz.getFullyQualifiedName())) {
     return MetaClassFactory.get(long.class);
   } else if (Double.class.getName().equals(clazz.getFullyQualifiedName())) {
     return MetaClassFactory.get(double.class);
   } else if (Float.class.getName().equals(clazz.getFullyQualifiedName())) {
     return MetaClassFactory.get(float.class);
   } else if (Short.class.getName().equals(clazz.getFullyQualifiedName())) {
     return MetaClassFactory.get(short.class);
   } else if (Character.class.getName().equals(clazz.getFullyQualifiedName())) {
     return MetaClassFactory.get(char.class);
   } else if (Byte.class.getName().equals(clazz.getFullyQualifiedName())) {
     return MetaClassFactory.get(byte.class);
   }
   return clazz;
 }
コード例 #10
0
ファイル: ReadMapping.java プロジェクト: lincolnthree/errai
  @Override
  public MetaClassMember getReadingMember() {
    if (readingMember != null) {
      return readingMember;
    }

    MetaMethod meth = toMap.getMethod(getterMethod, new MetaClass[0]);

    meth.asMethod().setAccessible(true);

    readingMember = meth;

    if (readingMember == null) {
      throw new RuntimeException(
          "no such getter method: " + toMap.getFullyQualifiedName() + "." + getterMethod);
    }

    return readingMember;
  }
コード例 #11
0
ファイル: ProxyMaker.java プロジェクト: pslegr/errai
  BuildMetaClass make(final String proxyClassName,
                      final MetaClass toProxy,
                      final String privateAccessorType) {
    final ClassStructureBuilder builder;



    final boolean renderEqualsAndHash;
    if (!toProxy.isInterface()) {
      renderEqualsAndHash = true;
      if (toProxy.isFinal()) {
        throw new UnproxyableClassException(toProxy, toProxy.getFullyQualifiedName()
            + " is an unproxiable class because it is final");
      }
      if (!toProxy.isDefaultInstantiable()) {
        throw new UnproxyableClassException(toProxy, toProxy.getFullyQualifiedName() + " must have a default " +
            "no-arg constructor");
      }

      builder = ClassBuilder.define(proxyClassName, toProxy).publicScope().body();
    }
    else {
      renderEqualsAndHash = false;
      builder = ClassBuilder.define(proxyClassName).publicScope().implementsInterface(toProxy).body();
    }

    final String proxyVar = "$$_proxy_$$";

    final Set<String> renderedMethods = new HashSet<String>();

    final Map<String, MetaType> typeVariableMap = new HashMap<String, MetaType>();
    final MetaParameterizedType metaParameterizedType = toProxy.getParameterizedType();

    if (metaParameterizedType != null) {
      int i = 0;
      for (final MetaTypeVariable metaTypeVariable : toProxy.getTypeParameters()) {
        typeVariableMap.put(metaTypeVariable.getName(), metaParameterizedType.getTypeParameters()[i++]);
      }
    }

    builder.privateField(proxyVar, toProxy).finish();

    final Set<Map.Entry<String, ProxyProperty>> entries = proxyProperties.entrySet();
    for (final Map.Entry<String, ProxyProperty> entry : entries) {
      builder.privateField(entry.getValue().getEncodedProperty(), entry.getValue().getType()).finish();
      builder.packageMethod(void.class, "$set_" + entry.getKey(), Parameter.of(entry.getValue().getType(), "o"))
          .append(Stmt.loadVariable(entry.getValue().getEncodedProperty()).assignValue(Refs.get("o")))
          .finish();
    }

    for (final MetaMethod method : toProxy.getMethods()) {
      final String methodString = GenUtil.getMethodString(method);
      if (renderedMethods.contains(methodString) || method.getName().equals("hashCode")
          || (method.getName().equals("equals") && method.getParameters().length == 1
          && method.getParameters()[0].getType().getFullyQualifiedName().equals(Object.class.getName()))) continue;

      renderedMethods.add(methodString);

      if ((!method.isPublic() && !method.isProtected()) ||
          method.isSynthetic() ||
          method.isFinal() ||
          method.isStatic() ||
          method.getDeclaringClass().getFullyQualifiedName().equals(Object.class.getName()))
        continue;

      final List<Parameter> methodParms = new ArrayList<Parameter>();
      final MetaParameter[] parameters = method.getParameters();

      for (int i = 0; i < parameters.length; i++) {
        methodParms.add(Parameter.of(parameters[i].getType().getErased(), "a" + i));
      }

      final DefParameters defParameters = DefParameters.fromParameters(methodParms);
      final BlockBuilder methBody = builder.publicMethod(method.getReturnType(), method.getName())
          .annotatedWith(OVERRIDE_ANNOTATION)
          .parameters(defParameters)
          .throws_(method.getCheckedExceptions());

      methBody.appendAll(getAroundInvokeStatements(method));
      methBody.appendAll(getBeforeStatements(method));

      final List<Parameter> parms = defParameters.getParameters();

      final Statement[] statementVars = new Statement[parms.size()];
      for (int i = 0; i < parms.size(); i++) {
        statementVars[i] = loadVariable(parms.get(i).getName());
      }

      if (!method.isPublic()) {
        PrivateAccessUtil.addPrivateAccessStubs(privateAccessorType, builder, method, new Modifier[0]);

        final Statement[] privateAccessStmts = new Statement[statementVars.length + 1];
        privateAccessStmts[0] = Refs.get(proxyVar);
        System.arraycopy(statementVars, 0, privateAccessStmts, 1, statementVars.length);

        if (method.getReturnType().isVoid()) {
          methBody._(loadVariable("this").invoke(PrivateAccessUtil.getPrivateMethodName(method), privateAccessStmts));
        }
        else {
          methBody._(loadVariable("this").invoke(PrivateAccessUtil.getPrivateMethodName(method), privateAccessStmts).returnValue());
        }
      }
      else {
        if (method.getReturnType().isVoid()) {
          methBody._(loadVariable(proxyVar).invoke(method, statementVars));
        }
        else {
          methBody._(loadVariable(proxyVar).invoke(method, statementVars).returnValue());
        }
      }

      methBody.appendAll(getAfterStatements(method));
      methBody.appendAll(getAroundInvokeStatements(method));

      methBody.finish();
    }

    if (renderEqualsAndHash) {
      // implement hashCode()
      builder.publicMethod(int.class, "hashCode")
          .annotatedWith(OVERRIDE_ANNOTATION)
          .body()
          ._(
              If.isNull(loadVariable(proxyVar))
                  ._(throw_(IllegalStateException.class, "call to hashCode() on an unclosed proxy."))
                  .finish()
                  .else_()
                  ._(Stmt.loadVariable(proxyVar).invoke("hashCode").returnValue())
                  .finish()
          )
          .finish();

      // implements equals()
      builder.publicMethod(boolean.class, "equals", Parameter.of(Object.class, "o"))
          .annotatedWith(OVERRIDE_ANNOTATION)
          .body()
          ._(
              If.isNull(loadVariable(proxyVar))
                  ._(throw_(IllegalStateException.class, "call to equals() on an unclosed proxy."))
                  .finish()
                  .else_()
                  ._(Stmt.loadVariable(proxyVar).invoke("equals", Refs.get("o")).returnValue())
                  .finish()
          )
          .finish();
    }

    builder.publicMethod(void.class, PROXY_BIND_METHOD).parameters(DefParameters.of(Parameter.of(toProxy, "proxy")))
        ._(loadVariable(proxyVar).assignValue(loadVariable("proxy"))).finish();

    return builder.getClassDefinition();
  }
コード例 #12
0
ファイル: EnvUtil.java プロジェクト: noter/errai
  public static ReachableTypes getAllReachableClasses(final GeneratorContext context) {
    if (System.getProperty(SYSPROP_USE_REACHABILITY_ANALYSIS) == null
        || !Boolean.getBoolean(SYSPROP_USE_REACHABILITY_ANALYSIS)) {

      log.warn("reachability analysis disabled. errai may generate unnecessary code.");
      log.warn(
          "enable reachability analysis with -D" + SYSPROP_USE_REACHABILITY_ANALYSIS + "=true");
      return ReachableTypes.EVERYTHING_REACHABLE_INSTANCE;
    }

    ReachabilityCache cache;
    if (reachabilityCache == null || (cache = reachabilityCache.get()) == null) {
      reachabilityCache = new SoftReference<ReachabilityCache>(cache = new ReachabilityCache());
    }

    if (cache.isCacheValid(context)) {
      return cache.getCache(context);
    }

    final EnvironmentConfig config = getEnvironmentConfig();

    long time = System.currentTimeMillis();

    final Set<String> packages = new HashSet<String>();

    if (isJUnitTest()) {
      packages.addAll(RebindUtils.findTranslatablePackagesInModule(context));
    } else {
      packages.addAll(RebindUtils.getOuterTranslatablePackages(context));
    }

    class Reachability {
      private final Set<String> packages;
      private final Set<String> negativeHits = new HashSet<String>();

      Reachability(final Set<String> packages) {
        this.packages = new HashSet<String>(packages);
      }

      public boolean isReachablePackage(final String pkg) {
        if (pkg == null || packages.contains(pkg)) {
          return true;
        }
        if (negativeHits.contains(pkg)) {
          return false;
        }

        for (final String p : packages) {
          if (pkg.startsWith(p)) {
            packages.add(pkg);
            return true;
          }
        }

        negativeHits.add(pkg);
        return false;
      }
    }

    final Set<String> allDependencies =
        Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(100));
    final Collection<MetaClass> allCachedClasses = MetaClassFactory.getAllCachedClasses();
    final ClassLoader classLoader = EnvUtil.class.getClassLoader();

    final ExecutorService executor =
        Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    final Reachability reachability = new Reachability(packages);

    try {
      for (final MetaClass mc : allCachedClasses) {
        String fullyQualifiedName = mc.getFullyQualifiedName();
        int splitPoint;
        while ((splitPoint = fullyQualifiedName.lastIndexOf('$')) != -1) {
          fullyQualifiedName = fullyQualifiedName.substring(0, splitPoint);
        }

        if (mc.isPrimitive() || mc.isArray()) {
          continue;
        } else if (isReachabilityExcluded(mc.getPackageName())) {
          continue;
        } else if (!config.getExplicitTypes().contains(fullyQualifiedName)
            && !reachability.isReachablePackage(mc.getPackageName())) {
          continue;
        }

        final URL resource =
            classLoader.getResource(fullyQualifiedName.replaceAll("\\.", "/") + ".java");

        if (resource != null) {
          InputStream stream = null;
          try {
            stream = new BufferedInputStream(resource.openStream());
            final byte[] readBuffer = new byte[stream.available()];
            stream.read(readBuffer);

            if (log.isDebugEnabled()) {
              log.debug("scanning " + fullyQualifiedName + " for reachable types ...");
            }
            executor.execute(new ReachabilityRunnable(readBuffer, allDependencies));
          } catch (IOException e) {
            log.warn("could not open resource: " + resource.getFile());
          } finally {
            if (stream != null) {
              stream.close();
            }
          }
        } else {
          log.warn("source for " + fullyQualifiedName + " is missing.");
        }
      }
    } catch (Throwable e) {
      e.printStackTrace();
    }

    try {
      executor.shutdown();
      executor.awaitTermination(60, TimeUnit.MINUTES);
    } catch (InterruptedException e) {
      log.warn("the reachability analysis was interrupted", e);
      cache.putCache(context, ReachableTypes.EVERYTHING_REACHABLE_INSTANCE);
      return ReachableTypes.EVERYTHING_REACHABLE_INSTANCE;
    }

    if (log.isDebugEnabled()) {
      log.debug("*** REACHABILITY ANALYSIS (production mode: " + EnvUtil.isProdMode() + ") ***");
      for (final String s : allDependencies) {
        log.debug(" -> " + s);
      }

      time = System.currentTimeMillis() - time;

      log.debug("*** END OF REACHABILITY ANALYSIS (" + time + "ms) *** ");
    }

    final ReachableTypes reachableTypes = new ReachableTypes(allDependencies, true);
    cache.putCache(context, reachableTypes);
    return reachableTypes;
  }
コード例 #13
0
 @Override
 public String toString() {
   return type.getFullyQualifiedName();
 }
コード例 #14
0
  private void appendPageShowMethod(
      AnonymousClassStructureBuilder pageImplBuilder,
      MetaClass pageClass,
      Class<? extends Annotation> annotation,
      boolean addPrivateAccessors) {
    BlockBuilder<?> method =
        pageImplBuilder
            .publicMethod(
                void.class,
                createMethodNameFromAnnotation(annotation),
                Parameter.of(pageClass, "widget"),
                Parameter.of(HistoryToken.class, "state"))
            .body();

    int idx = 0;

    method.append(Stmt.declareFinalVariable("pageState", Map.class, new HashMap<String, Object>()));
    for (MetaField field : pageClass.getFieldsAnnotatedWith(PageState.class)) {
      PageState psAnno = field.getAnnotation(PageState.class);
      String fieldName = field.getName();
      String queryParamName = psAnno.value();
      if (queryParamName == null || queryParamName.trim().isEmpty()) {
        queryParamName = fieldName;
      }

      if (addPrivateAccessors) {
        PrivateAccessUtil.addPrivateAccessStubs(
            PrivateAccessType.Write, "jsni", pageImplBuilder, field, new Modifier[] {});
      }

      String injectorName = PrivateAccessUtil.getPrivateFieldInjectorName(field);

      MetaClass erasedFieldType = field.getType().getErased();
      if (erasedFieldType.isAssignableTo(Collection.class)) {
        MetaClass elementType =
            MarshallingGenUtil.getConcreteCollectionElementType(field.getType());
        if (elementType == null) {
          throw new UnsupportedOperationException(
              "Found a @PageState field with a Collection type but without a concrete type parameter. "
                  + "Collection-typed @PageState fields must specify a concrete type parameter.");
        }
        if (erasedFieldType.equals(MetaClassFactory.get(Set.class))) {
          method.append(Stmt.declareVariable(fieldName, Stmt.newObject(HashSet.class)));
        } else if (erasedFieldType.equals(MetaClassFactory.get(List.class))
            || erasedFieldType.equals(MetaClassFactory.get(Collection.class))) {
          method.append(Stmt.declareVariable(fieldName, Stmt.newObject(ArrayList.class)));
        } else {
          throw new UnsupportedOperationException(
              "Found a @PageState field which is a collection of type "
                  + erasedFieldType.getFullyQualifiedName()
                  + ". For collection-valued fields, only the exact types java.util.Collection, java.util.Set, and "
                  + "java.util.List are supported at this time.");
        }

        // for (String fv{idx} : state.get({fieldName}))
        method.append(
            Stmt.loadVariable("state")
                .invoke("getState")
                .invoke("get", queryParamName)
                .foreach("elem", Object.class)
                .append(
                    Stmt.declareVariable(
                        "fv" + idx, Stmt.castTo(String.class, Stmt.loadVariable("elem"))))
                .append(
                    Stmt.loadVariable(fieldName)
                        .invoke(
                            "add",
                            paramFromStringStatement(elementType, Stmt.loadVariable("fv" + idx))))
                .append(
                    Stmt.loadVariable("pageState")
                        .invoke("put", fieldName, Stmt.loadVariable(fieldName)))
                .finish());
        method.append(
            Stmt.loadVariable("this")
                .invoke(injectorName, Stmt.loadVariable("widget"), Stmt.loadVariable(fieldName)));
      } else {
        method.append(
            Stmt.declareFinalVariable(
                "fv" + idx,
                Collection.class,
                Stmt.loadVariable("state").invoke("getState").invoke("get", queryParamName)));
        method.append(
            If.cond(
                    Bool.or(
                        Bool.isNull(Stmt.loadVariable("fv" + idx)),
                        Stmt.loadVariable("fv" + idx).invoke("isEmpty")))
                .append(
                    Stmt.loadVariable("this")
                        .invoke(
                            injectorName,
                            Stmt.loadVariable("widget"),
                            defaultValueStatement(erasedFieldType)))
                .finish()
                .else_()
                .append(
                    Stmt.loadVariable("this")
                        .invoke(
                            injectorName,
                            Stmt.loadVariable("widget"),
                            paramFromStringStatement(
                                erasedFieldType,
                                Stmt.loadVariable("fv" + idx).invoke("iterator").invoke("next"))))
                .append(
                    Stmt.loadVariable("pageState")
                        .invoke(
                            "put",
                            fieldName,
                            Stmt.loadVariable("fv" + idx).invoke("iterator").invoke("next")))
                .finish());
      }
      idx++;
    }

    if (addPrivateAccessors) {
      method.append(
          Stmt.invokeStatic(
              CDI.class,
              "fireEvent",
              Stmt.newObject(NavigationEvent.class)
                  .withParameters(
                      Stmt.newObject(PageRequest.class)
                          .withParameters(
                              getPageName(pageClass), Stmt.loadVariable("pageState")))));
    }

    checkMethodAndAddPrivateAccessors(
        pageImplBuilder, method, pageClass, annotation, HistoryToken.class, "state");

    method.finish();
  }
コード例 #15
0
  @Override
  protected String generate(TreeLogger logger, GeneratorContext context) {
    final ClassStructureBuilder<?> classBuilder =
        Implementations.extend(NavigationGraph.class, GENERATED_CLASS_NAME);

    // accumulation of (name, pageclass) mappings for dupe detection and dot file generation
    BiMap<String, MetaClass> pageNames = HashBiMap.create();

    // accumulation UniquePageRoles for ensuring there is exactly one.
    Multimap<Class<?>, MetaClass> pageRoles = ArrayListMultimap.create();

    ConstructorBlockBuilder<?> ctor = classBuilder.publicConstructor();
    final Collection<MetaClass> pages = ClassScanner.getTypesAnnotatedWith(Page.class, context);
    for (MetaClass pageClass : pages) {
      if (!pageClass.isAssignableTo(IsWidget.class)) {
        throw new GenerationException(
            "Class "
                + pageClass.getFullyQualifiedName()
                + " is annotated with @Page, so it must implement IsWidget");
      }
      Page annotation = pageClass.getAnnotation(Page.class);
      String pageName = getPageName(pageClass);
      List<Class<? extends PageRole>> annotatedPageRoles = Arrays.asList(annotation.role());

      MetaClass prevPageWithThisName = pageNames.put(pageName, pageClass);
      if (prevPageWithThisName != null) {
        throw new GenerationException(
            "Page names must be unique, but "
                + prevPageWithThisName
                + " and "
                + pageClass
                + " are both named ["
                + pageName
                + "]");
      }
      Statement pageImplStmt = generateNewInstanceOfPageImpl(pageClass, pageName);
      if (annotatedPageRoles.contains(DefaultPage.class)) {
        // need to assign the page impl to a variable and add it to the map twice
        ctor.append(Stmt.declareFinalVariable("defaultPage", PageNode.class, pageImplStmt));
        pageImplStmt = Variable.get("defaultPage");
        ctor.append(Stmt.nestedCall(Refs.get("pagesByName")).invoke("put", "", pageImplStmt));
        ctor.append(
            Stmt.nestedCall(Refs.get("pagesByRole"))
                .invoke("put", DefaultPage.class, pageImplStmt));
      } else if (pageName.equals("")) {
        throw new GenerationException(
            "Page "
                + pageClass.getFullyQualifiedName()
                + " has an empty path. Only the"
                + " page with startingPage=true is permitted to have an empty path.");
      }

      final String fieldName = StringUtils.uncapitalize(pageClass.getName());
      ctor.append(Stmt.declareFinalVariable(fieldName, PageNode.class, pageImplStmt));
      ctor.append(
          Stmt.nestedCall(Refs.get("pagesByName")).invoke("put", pageName, Refs.get(fieldName)));

      for (Class<? extends PageRole> annotatedPageRole : annotatedPageRoles) {
        pageRoles.put(annotatedPageRole, pageClass);
        // DefaultPage is already added above.
        if (!annotatedPageRole.equals(DefaultPage.class))
          ctor.append(
              Stmt.nestedCall(Refs.get("pagesByRole"))
                  .invoke("put", annotatedPageRole, Refs.get(fieldName)));
      }
    }
    ctor.finish();

    renderNavigationToDotFile(pageNames);

    validateDefaultPagePresent(pages, pageRoles);
    validateUnique(pageRoles);

    return classBuilder.toJavaString();
  }