/**
  * Given a parameterized type and a generic type information, aligns actual type parameters. For
  * example, if a class uses generic type
  *
  * <pre>&lt;T,U,V&gt;</pre>
  *
  * (redirectGenericTypes), is used with actual type parameters
  *
  * <pre>&lt;java.lang.String, U,V&gt;</pre>
  *
  * , then a class or interface using generic types
  *
  * <pre>&lt;T,V&gt;</pre>
  *
  * will be aligned to
  *
  * <pre>&lt;java.lang.String,V&gt;</pre>
  *
  * @param redirectGenericTypes the type arguments or the redirect class node
  * @param parameterizedTypes the actual type arguments used on this class node
  * @param alignmentTarget the generic type arguments to which we want to align to
  * @return aligned type arguments
  */
 public static GenericsType[] alignGenericTypes(
     final GenericsType[] redirectGenericTypes,
     final GenericsType[] parameterizedTypes,
     final GenericsType[] alignmentTarget) {
   if (alignmentTarget == null) return EMPTY_GENERICS_ARRAY;
   if (parameterizedTypes == null || parameterizedTypes.length == 0) return alignmentTarget;
   GenericsType[] generics = new GenericsType[alignmentTarget.length];
   for (int i = 0, scgtLength = alignmentTarget.length; i < scgtLength; i++) {
     final GenericsType currentTarget = alignmentTarget[i];
     GenericsType match = null;
     if (redirectGenericTypes != null) {
       for (int j = 0; j < redirectGenericTypes.length && match == null; j++) {
         GenericsType redirectGenericType = redirectGenericTypes[j];
         if (redirectGenericType.isCompatibleWith(currentTarget.getType())) {
           if (currentTarget.isPlaceholder()
               && redirectGenericType.isPlaceholder()
               && !currentTarget.getName().equals(redirectGenericType.getName())) {
             // check if there's a potential better match
             boolean skip = false;
             for (int k = j + 1; k < redirectGenericTypes.length && !skip; k++) {
               GenericsType ogt = redirectGenericTypes[k];
               if (ogt.isPlaceholder()
                   && ogt.isCompatibleWith(currentTarget.getType())
                   && ogt.getName().equals(currentTarget.getName())) {
                 skip = true;
               }
             }
             if (skip) continue;
           }
           match = parameterizedTypes[j];
           if (currentTarget.isWildcard()) {
             // if alignment target is a wildcard type
             // then we must make best effort to return a parameterized
             // wildcard
             ClassNode lower = currentTarget.getLowerBound() != null ? match.getType() : null;
             ClassNode[] currentUpper = currentTarget.getUpperBounds();
             ClassNode[] upper = currentUpper != null ? new ClassNode[currentUpper.length] : null;
             if (upper != null) {
               for (int k = 0; k < upper.length; k++) {
                 upper[k] =
                     currentUpper[k].isGenericsPlaceHolder() ? match.getType() : currentUpper[k];
               }
             }
             match = new GenericsType(ClassHelper.makeWithoutCaching("?"), upper, lower);
             match.setWildcard(true);
           }
         }
       }
     }
     if (match == null) {
       match = currentTarget;
     }
     generics[i] = match;
   }
   return generics;
 }
 private static void writeGenericsBounds(
     StringBuffer ret, GenericsType type, boolean writeInterfaceMarker) {
   if (type.getUpperBounds() != null) {
     ClassNode[] bounds = type.getUpperBounds();
     for (int i = 0; i < bounds.length; i++) {
       writeGenericsBoundType(ret, bounds[i], writeInterfaceMarker);
     }
   } else if (type.getLowerBound() != null) {
     writeGenericsBoundType(ret, type.getLowerBound(), writeInterfaceMarker);
   } else {
     writeGenericsBoundType(ret, type.getType(), writeInterfaceMarker);
   }
 }