/**
   * Same as {@link #setParentConfiguration(Configuration)}.
   *
   * @throws IllegalArgumentException if the argument is {@code null} or not a {@link
   *     Configuration}.
   */
  @Override
  void setParent(Configurable cfg) {
    NullArgumentException.check("cfg", cfg);
    if (!(cfg instanceof Configuration)) {
      throw new IllegalArgumentException(
          "The parent of a TemplateConfigurer can only be a Configuration");
    }

    if (parentConfigurationSet) {
      if (getParent() != cfg) {
        throw new IllegalStateException(
            "This TemplateConfigurer is already associated with a different Configuration instance.");
      }
      return;
    }

    if (((Configuration) cfg).getIncompatibleImprovements().intValue()
            < _TemplateAPI.VERSION_INT_2_3_22
        && hasAnyConfigurableSet()) {
      throw new IllegalStateException(
          "This TemplateConfigurer can't be associated to a Configuration that has incompatibleImprovements "
              + "less than 2.3.22, because it changes non-parser settings.");
    }

    super.setParent(cfg);
    parentConfigurationSet = true;
  }
  /** Creates a plain text (unparsed) template. */
  private UnboundTemplate(String content, String sourceName, Configuration cfg) {
    NullArgumentException.check(cfg);
    this.cfg = cfg;
    this.sourceName = sourceName;
    this.templateLanguageVersion =
        normalizeTemplateLanguageVersion(cfg.getIncompatibleImprovements());
    this.templateSpecifiedEncoding = null;

    rootElement = new TextBlock(content);
    actualTagSyntax = cfg.getTagSyntax();
    actualNamingConvention = cfg.getNamingConvention();
  }
  /**
   * @param reader Reads the template source code
   * @param cfg The FreeMarker configuration settings; some of them influences parsing, also the
   *     resulting {@link UnboundTemplate} will be bound to this.
   * @param assumedEncoding This is the name of the charset that we are supposed to be using. This
   *     is only needed to check if the encoding specified in the {@code #ftl} header (if any)
   *     matches this. If this is non-{@code null} and they don't match, a {@link
   *     WrongEncodingException} will be thrown by the parser.
   * @param sourceName Shown in error messages as the template "file" location.
   */
  UnboundTemplate(Reader reader, String sourceName, Configuration cfg, String assumedEncoding)
      throws IOException {
    NullArgumentException.check(cfg);
    this.cfg = cfg;
    this.sourceName = sourceName;
    this.templateLanguageVersion =
        normalizeTemplateLanguageVersion(cfg.getIncompatibleImprovements());

    LineTableBuilder ltbReader;
    try {
      if (!(reader instanceof BufferedReader)) {
        reader = new BufferedReader(reader, 0x1000);
      }
      ltbReader = new LineTableBuilder(reader);
      reader = ltbReader;

      try {
        FMParser parser =
            new FMParser(
                this,
                reader,
                assumedEncoding,
                cfg.getStrictSyntaxMode(),
                cfg.getWhitespaceStripping(),
                cfg.getTagSyntax(),
                cfg.getNamingConvention(),
                cfg.getIncompatibleImprovements().intValue());

        TemplateElement rootElement;
        try {
          rootElement = parser.Root();
        } catch (IndexOutOfBoundsException exc) {
          // There's a JavaCC bug where the Reader throws a RuntimeExcepton and then JavaCC fails
          // with
          // IndexOutOfBoundsException. If that wasn't the case, we just rethrow. Otherwise we
          // suppress the
          // IndexOutOfBoundsException and let the real cause to be thrown later.
          if (!ltbReader.hasFailure()) {
            throw exc;
          }
          rootElement = null;
        }
        this.rootElement = rootElement;

        this.actualTagSyntax = parser._getLastTagSyntax();
        this.actualNamingConvention = parser._getLastNamingConvention();
        this.templateSpecifiedEncoding = parser._getTemplateSpecifiedEncoding();
      } catch (TokenMgrError exc) {
        // TokenMgrError VS ParseException is not an interesting difference for the user, so we just
        // convert it
        // to ParseException
        throw exc.toParseException(this);
      }
    } catch (ParseException e) {
      e.setTemplateName(getSourceName());
      throw e;
    } finally {
      reader.close();
    }

    // Throws any exception that JavaCC has silently treated as EOF:
    ltbReader.throwFailure();

    if (prefixToNamespaceURIMapping != null) {
      prefixToNamespaceURIMapping = Collections.unmodifiableMap(prefixToNamespaceURIMapping);
      namespaceURIToPrefixMapping = Collections.unmodifiableMap(namespaceURIToPrefixMapping);
    }
  }
  /**
   * Updates the content of the {@link #typeFlagsByParamCount} field with the parameter type flags
   * of a method. Don't call this when {@link #bugfixed} is {@code false}!
   *
   * @param dstParamCount The parameter count for which we want to merge in the type flags
   * @param srcTypeFlagsByParamIdx If shorter than {@code dstParamCount}, its last item will be
   *     repeated until dstParamCount length is reached. If longer, the excessive items will be
   *     ignored. Maybe {@link #ALL_ZEROS_ARRAY}. Maybe a 0-length array. Can't be {@code null}.
   */
  protected final void mergeInTypesFlags(int dstParamCount, int[] srcTypeFlagsByParamIdx) {
    NullArgumentException.check("srcTypesFlagsByParamIdx", srcTypeFlagsByParamIdx);

    // Special case of 0 param count:
    if (dstParamCount == 0) {
      if (typeFlagsByParamCount == null) {
        typeFlagsByParamCount = ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY;
      } else if (typeFlagsByParamCount != ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY) {
        typeFlagsByParamCount[0] = ALL_ZEROS_ARRAY;
      }
      return;
    }

    // Ensure that typesFlagsByParamCount[dstParamCount] exists:
    if (typeFlagsByParamCount == null) {
      typeFlagsByParamCount = new int[dstParamCount + 1][];
    } else if (typeFlagsByParamCount.length <= dstParamCount) {
      int[][] newTypeFlagsByParamCount = new int[dstParamCount + 1][];
      System.arraycopy(
          typeFlagsByParamCount, 0, newTypeFlagsByParamCount, 0, typeFlagsByParamCount.length);
      typeFlagsByParamCount = newTypeFlagsByParamCount;
    }

    int[] dstTypeFlagsByParamIdx = typeFlagsByParamCount[dstParamCount];
    if (dstTypeFlagsByParamIdx == null) {
      // This is the first method added with this number of params => no merging

      if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) {
        int srcParamCount = srcTypeFlagsByParamIdx.length;
        dstTypeFlagsByParamIdx = new int[dstParamCount];
        for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
          dstTypeFlagsByParamIdx[paramIdx] =
              srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1];
        }
      } else {
        dstTypeFlagsByParamIdx = ALL_ZEROS_ARRAY;
      }

      typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx;
    } else {
      // dstTypeFlagsByParamIdx != null, so we need to merge into it.

      if (srcTypeFlagsByParamIdx == dstTypeFlagsByParamIdx) {
        // Used to occur when both are ALL_ZEROS_ARRAY
        return;
      }

      // As we will write dstTypeFlagsByParamIdx, it can't remain ALL_ZEROS_ARRAY anymore.
      if (dstTypeFlagsByParamIdx == ALL_ZEROS_ARRAY && dstParamCount > 0) {
        dstTypeFlagsByParamIdx = new int[dstParamCount];
        typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx;
      }

      for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) {
        final int srcParamTypeFlags;
        if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) {
          int srcParamCount = srcTypeFlagsByParamIdx.length;
          srcParamTypeFlags =
              srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1];
        } else {
          srcParamTypeFlags = 0;
        }

        final int dstParamTypesFlags = dstTypeFlagsByParamIdx[paramIdx];
        if (dstParamTypesFlags != srcParamTypeFlags) {
          int mergedTypeFlags = dstParamTypesFlags | srcParamTypeFlags;
          if ((mergedTypeFlags & TypeFlags.MASK_ALL_NUMERICALS) != 0) {
            // Must not be set if we don't have numerical type at this index!
            mergedTypeFlags |= TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT;
          }
          dstTypeFlagsByParamIdx[paramIdx] = mergedTypeFlags;
        }
      }
    }
  }
 /**
  * When the standard template loading/caching mechanism is used, this forces the charset used for
  * reading the template "file", overriding everything but the encoding coming from the {@code
  * #ftl} header. This setting overrides the locale-specific encodings set via {@link
  * Configuration#setEncoding(java.util.Locale, String)}. It also overrides the {@code encoding}
  * parameter of {@link Configuration#getTemplate(String, String)} (and of its overloads) and the
  * {@code encoding} parameter of the {@code #include} directive. This works like that because
  * specifying the encoding where you are requesting the template is error prone and deprecated.
  *
  * <p>If you are developing your own template loading/caching mechanism instead of the standard
  * one, note that the above behavior is not guaranteed by this class alone; you have to ensure it.
  * Also, read the note on {@code encoding} in the documentation of {@link #configure(Template)}.
  */
 public void setEncoding(String encoding) {
   NullArgumentException.check("encoding", encoding);
   this.encoding = encoding;
 }
 /**
  * Sets the output format of the template; see {@link Configuration#setOutputFormat(OutputFormat)}
  * for more.
  */
 public void setOutputFormat(OutputFormat outputFormat) {
   NullArgumentException.check("outputFormat", outputFormat);
   this.outputFormat = outputFormat;
 }