/*
 * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
 */

package play.data.format;

import org.springframework.core.*;
import org.springframework.core.convert.*;
import org.springframework.context.i18n.*;
import org.springframework.format.support.*;
import org.springframework.core.convert.converter.*;

import java.util.*;

import java.lang.annotation.*;
import java.lang.reflect.*;

import javax.inject.Inject;
import javax.inject.Singleton;

import play.i18n.MessagesApi;

/** Formatters helper. */
@Singleton
public class Formatters {

  @Inject
  public Formatters(MessagesApi messagesApi) {
    // By default, we always register some common and useful Formatters
    register(Date.class, new Formats.DateFormatter(messagesApi));
    register(Date.class, new Formats.AnnotationDateFormatter(messagesApi));
    register(String.class, new Formats.AnnotationNonEmptyFormatter());
    registerOptional();
  }

  /**
   * Parses this string as instance of the given class.
   *
   * @param text the text to parse
   * @param clazz class representing the required type
   * @param <T> the type to parse out of the text
   * @return the parsed value
   */
  public <T> T parse(String text, Class<T> clazz) {
    return conversion.convert(text, clazz);
  }

  /**
   * Parses this string as instance of a specific field
   *
   * @param field the related field (custom formatters are extracted from this field annotation)
   * @param text the text to parse
   * @param <T> the type to parse out of the text
   * @return the parsed value
   */
  @SuppressWarnings("unchecked")
  public <T> T parse(Field field, String text) {
    return (T) conversion.convert(text, new TypeDescriptor(field));
  }

  /**
   * Computes the display string for any value.
   *
   * @param t the value to print
   * @param <T> the type to print
   * @return the formatted string
   */
  public <T> String print(T t) {
    if (t == null) {
      return "";
    }
    if (conversion.canConvert(t.getClass(), String.class)) {
      return conversion.convert(t, String.class);
    } else {
      return t.toString();
    }
  }

  /**
   * Computes the display string for any value, for a specific field.
   *
   * @param field the related field - custom formatters are extracted from this field annotation
   * @param t the value to print
   * @param <T> the type to print
   * @return the formatted string
   */
  public <T> String print(Field field, T t) {
    return print(new TypeDescriptor(field), t);
  }

  /**
   * Computes the display string for any value, for a specific type.
   *
   * @param desc the field descriptor - custom formatters are extracted from this descriptor.
   * @param t the value to print
   * @param <T> the type to print
   * @return the formatted string
   */
  public <T> String print(TypeDescriptor desc, T t) {
    if (t == null) {
      return "";
    }
    if (desc != null && conversion.canConvert(desc, TypeDescriptor.valueOf(String.class))) {
      return (String) conversion.convert(t, desc, TypeDescriptor.valueOf(String.class));
    } else if (conversion.canConvert(t.getClass(), String.class)) {
      return conversion.convert(t, String.class);
    } else {
      return t.toString();
    }
  }

  // --

  /** The underlying conversion service. */
  public final FormattingConversionService conversion = new FormattingConversionService();

  /**
   * Super-type for custom simple formatters.
   *
   * @param <T> the type that this formatter will parse and print
   */
  public abstract static class SimpleFormatter<T> {

    /**
     * Binds the field - constructs a concrete value from submitted data.
     *
     * @param text the field text
     * @param locale the current Locale
     * @throws java.text.ParseException if the text could not be parsed into T
     * @return a new value
     */
    public abstract T parse(String text, Locale locale) throws java.text.ParseException;

    /**
     * Unbinds this field - transforms a concrete value to plain string.
     *
     * @param t the value to unbind
     * @param locale the current Locale
     * @return printable version of the value
     */
    public abstract String print(T t, Locale locale);
  }

  /**
   * Super-type for annotation-based formatters.
   *
   * @param  the type of the annotation
   * @param <T> the type that this formatter will parse and print
   */
  public abstract static class AnnotationFormatter {

    /**
     * Binds the field - constructs a concrete value from submitted data.
     *
     * @param annotation the annotation that triggered this formatter
     * @param text the field text
     * @param locale the current Locale
     * @throws java.text.ParseException when the text could not be parsed
     * @return a new value
     */
    public abstract T parse(A annotation, String text, Locale locale)
        throws java.text.ParseException;

    /**
     * Unbind this field (ie. transform a concrete value to plain string)
     *
     * @param annotation the annotation that triggered this formatter.
     * @param value the value to unbind
     * @param locale the current Locale
     * @return printable version of the value
     */
    public abstract String print(A annotation, T value, Locale locale);
  }

  /** Converter for String -> Optional and Optional -> String */
  private Formatters registerOptional() {
    conversion.addConverter(
        new GenericConverter() {

          public Object convert(
              Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            if (sourceType.getObjectType().equals(String.class)) {
              // From String to Optional
              Object element =
                  conversion.convert(source, sourceType, targetType.elementTypeDescriptor(source));
              return Optional.ofNullable(element);
            } else if (targetType.getObjectType().equals(String.class)) {
              // From Optional to String
              if (source == null) return "";

              Optional<?> opt = (Optional) source;
              return opt.map(
                      o ->
                          conversion.convert(
                              source, sourceType.getElementTypeDescriptor(), targetType))
                  .orElse("");
            }
            return null;
          }

          public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
            Set<ConvertiblePair> result = new HashSet<>();
            result.add(new ConvertiblePair(Optional.class, String.class));
            result.add(new ConvertiblePair(String.class, Optional.class));
            return result;
          }
        });

    return this;
  }

  /**
   * Registers a simple formatter.
   *
   * @param clazz class handled by this formatter
   * @param <T> the type that this formatter will parse and print
   * @param formatter the formatter to register
   * @return the modified Formatters object.
   */
  public <T> Formatters register(final Class<T> clazz, final SimpleFormatter<T> formatter) {
    conversion.addFormatterForFieldType(
        clazz,
        new org.springframework.format.Formatter<T>() {

          public T parse(String text, Locale locale) throws java.text.ParseException {
            return formatter.parse(text, locale);
          }

          public String print(T t, Locale locale) {
            return formatter.print(t, locale);
          }

          public String toString() {
            return formatter.toString();
          }
        });

    return this;
  }

  /**
   * Registers an annotation-based formatter.
   *
   * @param clazz class handled by this formatter
   * @param formatter the formatter to register
   * @param  the annotation type
   * @param <T> the type that will be parsed or printed
   * @return the modified Formatters object.
   */
  @SuppressWarnings("unchecked")
  public  Formatters register(
      final Class<T> clazz, final AnnotationFormatter formatter) {
    final Class<? extends Annotation> annotationType =
        (Class<? extends Annotation>)
            GenericTypeResolver.resolveTypeArguments(
                formatter.getClass(), AnnotationFormatter.class)[0];

    conversion.addConverter(
        new ConditionalGenericConverter() {
          public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
            Set<GenericConverter.ConvertiblePair> types = new HashSet<>();
            types.add(new GenericConverter.ConvertiblePair(clazz, String.class));
            return types;
          }

          public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
            return (sourceType.getAnnotation(annotationType) != null);
          }

          public Object convert(
              Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            final A a = (A) sourceType.getAnnotation(annotationType);
            Locale locale = LocaleContextHolder.getLocale();
            try {
              return formatter.print(a, (T) source, locale);
            } catch (Exception ex) {
              throw new ConversionFailedException(sourceType, targetType, source, ex);
            }
          }

          public String toString() {
            return "@"
                + annotationType.getName()
                + " "
                + clazz.getName()
                + " -> "
                + String.class.getName()
                + ": "
                + formatter;
          }
        });

    conversion.addConverter(
        new ConditionalGenericConverter() {
          public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
            Set<GenericConverter.ConvertiblePair> types = new HashSet<>();
            types.add(new GenericConverter.ConvertiblePair(String.class, clazz));
            return types;
          }

          public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
            return (targetType.getAnnotation(annotationType) != null);
          }

          public Object convert(
              Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            final A a = (A) targetType.getAnnotation(annotationType);
            Locale locale = LocaleContextHolder.getLocale();
            try {
              return formatter.parse(a, (String) source, locale);
            } catch (Exception ex) {
              throw new ConversionFailedException(sourceType, targetType, source, ex);
            }
          }

          public String toString() {
            return String.class.getName()
                + " -> @"
                + annotationType.getName()
                + " "
                + clazz.getName()
                + ": "
                + formatter;
          }
        });

    return this;
  }
}


/*
 * Copyright 2016 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package play.core.cookie.encoding;

import java.util.*;

import static play.core.cookie.encoding.CookieUtil.*;

/**
 * A RFC6265 compliant cookie encoder to be used
 * server side, so some fields are sent (Version is typically ignored).
 *
 *
As Netty's Cookie merges Expires and MaxAge into one single field, only Max-Age field is sent.
 *
 *
Note that multiple cookies are supposed to be sent at once in a single "Set-Cookie" header.
 *
 * @see ServerCookieDecoder
 */
public final class ServerCookieEncoder extends CookieEncoder {

  /**
   * Strict encoder that validates that name and value chars are in the valid scope defined in
   * RFC6265
   */
  public static final ServerCookieEncoder STRICT = new ServerCookieEncoder(true);

  /** Lax instance that doesn't validate name and value */
  public static final ServerCookieEncoder LAX = new ServerCookieEncoder(false);

  private ServerCookieEncoder(boolean strict) {
    super(strict);
  }

  /**
   * Encodes the specified cookie name-value pair into a Set-Cookie header value.
   *
   * @param name the cookie name
   * @param value the cookie value
   * @return a single Set-Cookie header value
   */
  public String encode(String name, String value) {
    return encode(new DefaultCookie(name, value));
  }

  /**
   * Encodes the specified cookie into a Set-Cookie header value.
   *
   * @param cookie the cookie
   * @return a single Set-Cookie header value
   */
  public String encode(Cookie cookie) {
    if (cookie == null) {
      throw new NullPointerException("cookie");
    }
    final String name = cookie.name();
    final String value = cookie.value() != null ? cookie.value() : "";

    validateCookie(name, value);

    StringBuilder buf = new StringBuilder();

    if (cookie.wrap()) {
      addQuoted(buf, name, value);
    } else {
      add(buf, name, value);
    }

    if (cookie.maxAge() != Integer.MIN_VALUE) {
      add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge());
      Date expires =
          cookie.maxAge() <= 0
              ? new Date(0) // Set expires to the Unix epoch
              : new Date(cookie.maxAge() * 1000L + System.currentTimeMillis());
      add(buf, CookieHeaderNames.EXPIRES, HttpHeaderDateFormat.get().format(expires));
    }

    if (cookie.sameSite() != null) {
      add(buf, CookieHeaderNames.SAMESITE, cookie.sameSite());
    }

    if (cookie.path() != null) {
      add(buf, CookieHeaderNames.PATH, cookie.path());
    }

    if (cookie.domain() != null) {
      add(buf, CookieHeaderNames.DOMAIN, cookie.domain());
    }
    if (cookie.isSecure()) {
      add(buf, CookieHeaderNames.SECURE);
    }
    if (cookie.isHttpOnly()) {
      add(buf, CookieHeaderNames.HTTPONLY);
    }

    return stripTrailingSeparator(buf);
  }

  /**
   * Batch encodes cookies into Set-Cookie header values.
   *
   * @param cookies a bunch of cookies
   * @return the corresponding bunch of Set-Cookie headers
   */
  public List<String> encode(Cookie... cookies) {
    if (cookies == null) {
      throw new NullPointerException("cookies");
    }
    if (cookies.length == 0) {
      return Collections.emptyList();
    }

    List<String> encoded = new ArrayList<String>(cookies.length);
    for (Cookie c : cookies) {
      if (c == null) {
        break;
      }
      encoded.add(encode(c));
    }
    return encoded;
  }

  /**
   * Batch encodes cookies into Set-Cookie header values.
   *
   * @param cookies a bunch of cookies
   * @return the corresponding bunch of Set-Cookie headers
   */
  public List<String> encode(Collection<? extends Cookie> cookies) {
    if (cookies == null) {
      throw new NullPointerException("cookies");
    }
    if (cookies.isEmpty()) {
      return Collections.emptyList();
    }

    List<String> encoded = new ArrayList<String>(cookies.size());
    for (Cookie c : cookies) {
      if (c == null) {
        break;
      }
      encoded.add(encode(c));
    }
    return encoded;
  }

  /**
   * Batch encodes cookies into Set-Cookie header values.
   *
   * @param cookies a bunch of cookies
   * @return the corresponding bunch of Set-Cookie headers
   */
  public List<String> encode(Iterable<? extends Cookie> cookies) {
    if (cookies == null) {
      throw new NullPointerException("cookies");
    }
    if (cookies.iterator().hasNext()) {
      return Collections.emptyList();
    }

    List<String> encoded = new ArrayList<String>();
    for (Cookie c : cookies) {
      if (c == null) {
        break;
      }
      encoded.add(encode(c));
    }
    return encoded;
  }
}


/*
 * Copyright 2016 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package play.core.cookie.encoding;

import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;

/**
 * A RFC6265 compliant cookie decoder to be used
 * server side.
 *
 *
Only name and value fields are expected, so old fields are not populated (path, domain, etc).
 *
 *
Old RFC2965 cookies are still supported, old
 * fields will simply be ignored.
 *
 * @see ServerCookieEncoder
 */
public final class ServerCookieDecoder extends CookieDecoder {

  private static final String RFC2965_VERSION = "$Version";

  private static final String RFC2965_PATH = "$" + CookieHeaderNames.PATH;

  private static final String RFC2965_DOMAIN = "$" + CookieHeaderNames.DOMAIN;

  private static final String RFC2965_PORT = "$Port";

  /**
   * Strict encoder that validates that name and value chars are in the valid scope defined in
   * RFC6265
   */
  public static final ServerCookieDecoder STRICT = new ServerCookieDecoder(true);

  /** Lax instance that doesn't validate name and value */
  public static final ServerCookieDecoder LAX = new ServerCookieDecoder(false);

  private ServerCookieDecoder(boolean strict) {
    super(strict);
  }

  /**
   * Decodes the specified Set-Cookie HTTP header value into a {@link Cookie}.
   *
   * @param header the Set-Cookie header.
   * @return the decoded {@link Cookie}
   */
  public Set<Cookie> decode(String header) {
    if (header == null) {
      throw new NullPointerException("header");
    }
    final int headerLen = header.length();

    if (headerLen == 0) {
      return Collections.emptySet();
    }

    Set<Cookie> cookies = new TreeSet<Cookie>();

    int i = 0;

    boolean rfc2965Style = false;
    if (header.regionMatches(true, 0, RFC2965_VERSION, 0, RFC2965_VERSION.length())) {
      // RFC 2965 style cookie, move to after version value
      i = header.indexOf(';') + 1;
      rfc2965Style = true;
    }

    loop:
    for (; ; ) {

      // Skip spaces and separators.
      for (; ; ) {
        if (i == headerLen) {
          break loop;
        }
        char c = header.charAt(i);
        if (c == '\t' || c == '\n' || c == 0x0b || c == '\f' || c == '\r' || c == ' ' || c == ','
            || c == ';') {
          i++;
          continue;
        }
        break;
      }

      int nameBegin = i;
      int nameEnd = i;
      int valueBegin = -1;
      int valueEnd = -1;

      if (i != headerLen) {
        keyValLoop:
        for (; ; ) {

          char curChar = header.charAt(i);
          if (curChar == ';') {
            // NAME; (no value till ';')
            nameEnd = i;
            valueBegin = valueEnd = -1;
            break keyValLoop;

          } else if (curChar == '=') {
            // NAME=VALUE
            nameEnd = i;
            i++;
            if (i == headerLen) {
              // NAME= (empty value, i.e. nothing after '=')
              valueBegin = valueEnd = 0;
              break keyValLoop;
            }

            valueBegin = i;
            // NAME=VALUE;
            int semiPos = header.indexOf(';', i);
            valueEnd = i = semiPos > 0 ? semiPos : headerLen;
            break keyValLoop;
          } else {
            i++;
          }

          if (i == headerLen) {
            // NAME (no value till the end of string)
            nameEnd = headerLen;
            valueBegin = valueEnd = -1;
            break;
          }
        }
      }

      if (rfc2965Style
          && (header.regionMatches(nameBegin, RFC2965_PATH, 0, RFC2965_PATH.length())
              || header.regionMatches(nameBegin, RFC2965_DOMAIN, 0, RFC2965_DOMAIN.length())
              || header.regionMatches(nameBegin, RFC2965_PORT, 0, RFC2965_PORT.length()))) {

        // skip obsolete RFC2965 fields
        continue;
      }

      DefaultCookie cookie = initCookie(header, nameBegin, nameEnd, valueBegin, valueEnd);
      if (cookie != null) {
        cookies.add(cookie);
      }
    }

    return cookies;
  }
}


/*
 * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
 */

package play.mvc;

import akka.stream.javadsl.Source;
import akka.util.ByteString;
import play.core.j.JavaRangeResult;

import java.io.File;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Optional;

/**
 * Java API for Range results.
 *
 *
For reference, see RFC 7233.
 */
public class RangeResults {

  private static Optional<String> rangeHeader(Http.Request request) {
    return request.header(Http.HeaderNames.RANGE);
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param stream the content stream
   * @return range result if "Range" header is present and regular result if not
   * @deprecated Deprecated as of 2.7.0. Use {@link #ofStream(Http.Request, InputStream)} instead.
   */
  @Deprecated
  public static Result ofStream(InputStream stream) {
    return ofStream(Http.Context.current().request(), stream);
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param stream the content stream
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofStream(Http.Request request, InputStream stream) {
    return JavaRangeResult.ofStream(stream, rangeHeader(request), null, Optional.empty());
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param stream the content stream
   * @param contentLength the entity length
   * @return range result if "Range" header is present and regular result if not
   * @deprecated Deprecated as of 2.7.0. Use {@link #ofStream(Http.Request, InputStream, long)}
   *     instead.
   */
  @Deprecated
  public static Result ofStream(InputStream stream, long contentLength) {
    return ofStream(Http.Context.current().request(), stream, contentLength);
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param stream the content stream
   * @param contentLength the entity length
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofStream(Http.Request request, InputStream stream, long contentLength) {
    return JavaRangeResult.ofStream(
        contentLength, stream, rangeHeader(request), null, Optional.empty());
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param stream the content stream
   * @param contentLength the entity length
   * @param filename filename used at the Content-Disposition header
   * @return range result if "Range" header is present and regular result if not
   * @deprecated Deprecated as of 2.7.0. Use {@link #ofStream(Http.Request, InputStream, long,
   *     String)} instead.
   */
  @Deprecated
  public static Result ofStream(InputStream stream, long contentLength, String filename) {
    return ofStream(Http.Context.current().request(), stream, contentLength, filename);
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param stream the content stream
   * @param contentLength the entity length
   * @param filename filename used at the Content-Disposition header
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofStream(
      Http.Request request, InputStream stream, long contentLength, String filename) {
    return JavaRangeResult.ofStream(
        contentLength, stream, rangeHeader(request), filename, Optional.empty());
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param stream the content stream
   * @param contentLength the entity length
   * @param filename filename used at the Content-Disposition header
   * @param contentType the content type for this stream
   * @return range result if "Range" header is present and regular result if not
   * @deprecated Deprecated as of 2.7.0. Use {@link #ofStream(Http.Request, InputStream, long,
   *     String, String)} instead.
   */
  @Deprecated
  public static Result ofStream(
      InputStream stream, long contentLength, String filename, String contentType) {
    return ofStream(Http.Context.current().request(), stream, contentLength, filename, contentType);
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param stream the content stream
   * @param contentLength the entity length
   * @param filename filename used at the Content-Disposition header
   * @param contentType the content type for this stream
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofStream(
      Http.Request request,
      InputStream stream,
      long contentLength,
      String filename,
      String contentType) {
    return JavaRangeResult.ofStream(
        contentLength, stream, rangeHeader(request), filename, Optional.ofNullable(contentType));
  }

  /**
   * Returns the path as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param path the content path
   * @return range result if "Range" header is present and regular result if not
   * @deprecated Deprecated as of 2.7.0. Use {@link #ofPath(Http.Request, Path)} instead.
   */
  @Deprecated
  public static Result ofPath(Path path) {
    return ofPath(Http.Context.current().request(), path);
  }

  /**
   * Returns the path as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param path the content path
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofPath(Http.Request request, Path path) {
    return ofPath(request, path, StaticFileMimeTypes.fileMimeTypes());
  }

  /**
   * Returns the path as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param path the content path
   * @param fileMimeTypes Used for file type mapping.
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofPath(Http.Request request, Path path, FileMimeTypes fileMimeTypes) {
    return JavaRangeResult.ofPath(
        path, rangeHeader(request), fileMimeTypes.forFileName(path.toFile().getName()));
  }

  /**
   * Returns the path as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param path the content path
   * @param fileName filename used at the Content-Disposition header.
   * @return range result if "Range" header is present and regular result if not
   * @deprecated Deprecated as of 2.7.0. Use {link {@link #ofPath(Http.Request, Path, String)}
   *     instead.
   */
  @Deprecated
  public static Result ofPath(Path path, String fileName) {
    return ofPath(Http.Context.current().request(), path, fileName);
  }

  /**
   * Returns the path as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param path the content path
   * @param fileName filename used at the Content-Disposition header.
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofPath(Http.Request request, Path path, String fileName) {
    return ofPath(request, path, fileName, StaticFileMimeTypes.fileMimeTypes());
  }

  /**
   * Returns the path as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param path the content path
   * @param fileName filename used at the Content-Disposition header.
   * @param fileMimeTypes Used for file type mapping.
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofPath(
      Http.Request request, Path path, String fileName, FileMimeTypes fileMimeTypes) {
    return JavaRangeResult.ofPath(
        path, rangeHeader(request), fileName, fileMimeTypes.forFileName(fileName));
  }

  /**
   * Returns the file as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param file the content file
   * @return range result if "Range" header is present and regular result if not
   * @deprecated Deprecated as of 2.7.0. Use {@link #ofFile(Http.Request, File)} instead.
   */
  @Deprecated
  public static Result ofFile(File file) {
    return ofFile(Http.Context.current().request(), file);
  }

  /**
   * Returns the file as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param file the content file
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofFile(Http.Request request, File file) {
    return ofFile(request, file, StaticFileMimeTypes.fileMimeTypes());
  }

  /**
   * Returns the file as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param file the content file
   * @param fileMimeTypes Used for file type mapping.
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofFile(Http.Request request, File file, FileMimeTypes fileMimeTypes) {
    return JavaRangeResult.ofFile(
        file, rangeHeader(request), fileMimeTypes.forFileName(file.getName()));
  }

  /**
   * Returns the file as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param file the content file
   * @param fileName filename used at the Content-Disposition header
   * @return range result if "Range" header is present and regular result if not
   * @deprecated Deprecated as of 2.7.0. Use {@link #ofFile(Http.Request, File, String)} instead.
   */
  @Deprecated
  public static Result ofFile(File file, String fileName) {
    return ofFile(Http.Context.current().request(), file, fileName);
  }

  /**
   * Returns the file as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param file the content file
   * @param fileName filename used at the Content-Disposition header
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofFile(Http.Request request, File file, String fileName) {
    return ofFile(request, file, fileName, StaticFileMimeTypes.fileMimeTypes());
  }

  /**
   * Returns the file as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param file the content file
   * @param fileName filename used at the Content-Disposition header
   * @param fileMimeTypes Used for file type mapping.
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofFile(
      Http.Request request, File file, String fileName, FileMimeTypes fileMimeTypes) {
    return JavaRangeResult.ofFile(
        file, rangeHeader(request), fileName, fileMimeTypes.forFileName(fileName));
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param entityLength the entityLength
   * @param source source of the entity
   * @param fileName filename used at the Content-Disposition header
   * @param contentType the content type for this stream
   * @return range result if "Range" header is present and regular result if not
   * @deprecated Deprecated as of 2.7.0. Use {@link #ofSource(Http.Request, Long, Source, String,
   *     String)} instead.
   */
  @Deprecated
  public static Result ofSource(
      Long entityLength, Source<ByteString, ?> source, String fileName, String contentType) {
    return ofSource(Http.Context.current().request(), entityLength, source, fileName, contentType);
  }

  /**
   * Returns the stream as a result considering "Range" header. If the header is present and it is
   * satisfiable, then a Result containing just the requested part will be returned. If the header
   * is not present or is unsatisfiable, then a regular Result will be returned.
   *
   * @param request the request from which to retrieve the range header.
   * @param entityLength the entityLength
   * @param source source of the entity
   * @param fileName filename used at the Content-Disposition header
   * @param contentType the content type for this stream
   * @return range result if "Range" header is present and regular result if not
   */
  public static Result ofSource(
      Http.Request request,
      Long entityLength,
      Source<ByteString, ?> source,
      String fileName,
      String contentType) {
    return JavaRangeResult.ofSource(
        entityLength,
        source,
        rangeHeader(request),
        Optional.ofNullable(fileName),
        Optional.ofNullable(contentType));
  }
}


/*
 * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
 */

package play.libs;

import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.unmodifiableSet;
import static java.util.Objects.requireNonNull;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/** XPath for parsing */
public class XPath {

  static class PlayNamespaceContext implements NamespaceContext {

    private final Map<String, String> prefixMap = new HashMap<>();
    private final Map<String, Set<String>> namespaceMap = new HashMap<>();

    @Override
    public String getNamespaceURI(String prefix) {
      final String p = requireNonNull(prefix, "Null prefix");
      return Optional.of(prefixMap.get(p)).orElse(XMLConstants.NULL_NS_URI);
    }

    private Set<String> getPrefixesSet(String namespaceUri) {
      if (XMLConstants.XML_NS_URI.equals(namespaceUri)) {
        return singleton(XMLConstants.XML_NS_PREFIX);
      } else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceUri)) {
        return singleton(XMLConstants.XMLNS_ATTRIBUTE);
      } else {
        Set<String> prefixes = namespaceMap.get(namespaceUri);
        return prefixes != null ? unmodifiableSet(prefixes) : emptySet();
      }
    }

    @Override
    public String getPrefix(String namespaceURI) {
      final String uri = requireNonNull(namespaceURI, "Null namespaceURI");
      return getPrefixesSet(uri).stream().findFirst().orElse(null);
    }

    @Override
    public Iterator getPrefixes(String namespaceURI) {
      final String uri = requireNonNull(namespaceURI, "Null namespaceURI");
      return getPrefixesSet(uri).iterator();
    }

    void bindNamespaceUri(String prefix, String namespaceURI) {
      final String p = requireNonNull(prefix, "Null prefix");
      final String uri = requireNonNull(namespaceURI, "Null namespaceURI");
      if (!XMLConstants.DEFAULT_NS_PREFIX.equals(p)) {
        prefixMap.put(p, uri);
        Set<String> prefixSet = namespaceMap.computeIfAbsent(uri, k -> new LinkedHashSet<>());
        prefixSet.add(p);
      }
    }
  }

  /**
   * Select all nodes that are selected by this XPath expression. If multiple nodes match, multiple
   * nodes will be returned. Nodes will be returned in document-order,
   *
   * @param path the xpath expression
   * @param node the starting node
   * @param namespaces Namespaces that need to be available in the xpath, where the key is the
   *     prefix and the value the namespace URI
   * @return result of evaluating the xpath expression against node
   */
  public static NodeList selectNodes(String path, Object node, Map<String, String> namespaces) {
    try {
      XPathFactory factory = XPathFactory.newInstance();
      javax.xml.xpath.XPath xpath = factory.newXPath();

      if (namespaces != null) {
        PlayNamespaceContext nsContext = new PlayNamespaceContext();
        bindUnboundedNamespaces(nsContext, namespaces);
        xpath.setNamespaceContext(nsContext);
      }

      return (NodeList) xpath.evaluate(path, node, XPathConstants.NODESET);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Select all nodes that are selected by this XPath expression. If multiple nodes match, multiple
   * nodes will be returned. Nodes will be returned in document-order,
   *
   * @param path the xpath expression
   * @param node the starting node
   * @return result of evaluating the xpath expression against node
   */
  public static NodeList selectNodes(String path, Object node) {
    return selectNodes(path, node, null);
  }

  public static Node selectNode(String path, Object node, Map<String, String> namespaces) {
    try {
      XPathFactory factory = XPathFactory.newInstance();
      javax.xml.xpath.XPath xpath = factory.newXPath();

      if (namespaces != null) {
        PlayNamespaceContext nsContext = new PlayNamespaceContext();
        bindUnboundedNamespaces(nsContext, namespaces);
        xpath.setNamespaceContext(nsContext);
      }

      return (Node) xpath.evaluate(path, node, XPathConstants.NODE);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public static Node selectNode(String path, Object node) {
    return selectNode(path, node, null);
  }

  private static void bindUnboundedNamespaces(
      PlayNamespaceContext nsContext, Map<String, String> namespaces) {
    namespaces.forEach(
        (key, value) -> {
          if (nsContext.getPrefix(value) == null) {
            nsContext.bindNamespaceUri(key, value);
          }
        });
  }

  /**
   * @param path the XPath to execute
   * @param node the node, node-set or Context object for evaluation. This value can be null.
   * @param namespaces the XML namespaces map
   * @return the text of a node, or the value of an attribute
   */
  public static String selectText(String path, Object node, Map<String, String> namespaces) {
    try {
      XPathFactory factory = XPathFactory.newInstance();
      javax.xml.xpath.XPath xpath = factory.newXPath();

      if (namespaces != null) {
        PlayNamespaceContext nsContext = new PlayNamespaceContext();
        bindUnboundedNamespaces(nsContext, namespaces);
        xpath.setNamespaceContext(nsContext);
      }

      return (String) xpath.evaluate(path, node, XPathConstants.STRING);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * @param path the XPath to execute
   * @param node the node, node-set or Context object for evaluation. This value can be null.
   * @return the text of a node, or the value of an attribute
   */
  public static String selectText(String path, Object node) {
    return selectText(path, node, null);
  }
}


/*
 * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
 */

package play.libs;

class CrossScala {
    /**
     * Converts a Java List to Scala Seq.
     *
     * @param list    the java list.
     * @return the converted Seq.
     * @param <T> the element type.
     */
    public static <T> scala.collection.immutable.Seq<T> toSeq(java.util.List<T> list) {
        return scala.collection.JavaConverters.asScalaBufferConverter(list).asScala().toList();
    }

    /**
     * Converts a Java Array to Scala Seq.
     *
     * @param array    the java array.
     * @return the converted Seq.
     * @param <T> the element type.
     */
    public static <T> scala.collection.immutable.Seq<T> toSeq(T[] array) {
        return toSeq(java.util.Arrays.asList(array));
    }

    /**
     * Converts a Java varargs to Scala varargs.
     *
     * @param array    the java array.
     * @return the Scala varargs
     * @param <T> the element type.
     */
    @SafeVarargs
    public static <T> scala.collection.immutable.Seq<T> varargs(T... array) {
        return toSeq(array);
    }
}


/*
 * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
 */

package play.mvc;

import akka.stream.javadsl.Flow;
import akka.util.ByteString;
import com.fasterxml.jackson.databind.JsonNode;
import play.api.http.websocket.CloseCodes;
import play.http.websocket.Message;
import play.libs.F;
import play.libs.Scala;
import play.libs.streams.AkkaStreams;
import scala.PartialFunction;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;

/** A WebSocket handler. */
public abstract class WebSocket {

  /**
   * Invoke the WebSocket.
   *
   * @param request The request for the WebSocket.
   * @return A future of either a result to reject the WebSocket connection with, or a Flow to
   *     handle the WebSocket.
   */
  public abstract CompletionStage<F.Either<Result, Flow<Message, Message, ?>>> apply(
      Http.RequestHeader request);

  /** Acceptor for text WebSockets. */
  public static final MappedWebSocketAcceptor<String, String> Text =
      new MappedWebSocketAcceptor<>(
          Scala.partialFunction(
              message -> {
                if (message instanceof Message.Text) {
                  return F.Either.Left(((Message.Text) message).data());
                } else if (message instanceof Message.Binary) {
                  return F.Either.Right(
                      new Message.Close(
                          CloseCodes.Unacceptable(), "This websocket only accepts text frames"));
                } else {
                  throw Scala.noMatch();
                }
              }),
          Message.Text::new);

  /** Acceptor for binary WebSockets. */
  public static final MappedWebSocketAcceptor<ByteString, ByteString> Binary =
      new MappedWebSocketAcceptor<>(
          Scala.partialFunction(
              message -> {
                if (message instanceof Message.Binary) {
                  return F.Either.Left(((Message.Binary) message).data());
                } else if (message instanceof Message.Text) {
                  return F.Either.Right(
                      new Message.Close(
                          CloseCodes.Unacceptable(), "This websocket only accepts binary frames"));
                } else {
                  throw Scala.noMatch();
                }
              }),
          Message.Binary::new);

  /** Acceptor for JSON WebSockets. */
  public static final MappedWebSocketAcceptor<JsonNode, JsonNode> Json =
      new MappedWebSocketAcceptor<>(
          Scala.partialFunction(
              message -> {
                try {
                  if (message instanceof Message.Binary) {
                    return F.Either.Left(
                        play.libs.Json.parse(
                            ((Message.Binary) message).data().iterator().asInputStream()));
                  } else if (message instanceof Message.Text) {
                    return F.Either.Left(play.libs.Json.parse(((Message.Text) message).data()));
                  }
                } catch (RuntimeException e) {
                  return F.Either.Right(
                      new Message.Close(CloseCodes.Unacceptable(), "Unable to parse JSON message"));
                }
                throw Scala.noMatch();
              }),
          json -> new Message.Text(play.libs.Json.stringify(json)));

  /**
   * Acceptor for JSON WebSockets.
   *
   * @param in The class of the incoming messages, used to decode them from the JSON.
   * @param <In> The websocket's input type (what it receives from clients)
   * @param <Out> The websocket's output type (what it writes to clients)
   * @return The WebSocket acceptor.
   */
  public static <In, Out> MappedWebSocketAcceptor<In, Out> json(Class<In> in) {
    return new MappedWebSocketAcceptor<>(
        Scala.partialFunction(
            message -> {
              try {
                if (message instanceof Message.Binary) {
                  return F.Either.Left(
                      play.libs.Json.mapper()
                          .readValue(
                              ((Message.Binary) message).data().iterator().asInputStream(), in));
                } else if (message instanceof Message.Text) {
                  return F.Either.Left(
                      play.libs.Json.mapper().readValue(((Message.Text) message).data(), in));
                }
              } catch (Exception e) {
                return F.Either.Right(new Message.Close(CloseCodes.Unacceptable(), e.getMessage()));
              }
              throw Scala.noMatch();
            }),
        outMessage -> {
          try {
            return new Message.Text(play.libs.Json.mapper().writeValueAsString(outMessage));
          } catch (Exception e) {
            throw new RuntimeException(e);
          }
        });
  }

  /**
   * Utility class for creating WebSockets.
   *
   * @param <In> the type the websocket reads from clients (e.g. String, JsonNode)
   * @param <Out> the type the websocket outputs back to remote clients (e.g. String, JsonNode)
   */
  public static class MappedWebSocketAcceptor<In, Out> {
    private final PartialFunction<Message, F.Either<In, Message>> inMapper;
    private final Function<Out, Message> outMapper;

    public MappedWebSocketAcceptor(
        PartialFunction<Message, F.Either<In, Message>> inMapper,
        Function<Out, Message> outMapper) {
      this.inMapper = inMapper;
      this.outMapper = outMapper;
    }

    /**
     * Accept a WebSocket.
     *
     * @param f A function that takes the request header, and returns a future of either the result
     *     to reject the WebSocket connection with, or a flow to handle the WebSocket messages.
     * @return The WebSocket handler.
     */
    public WebSocket acceptOrResult(
        Function<Http.RequestHeader, CompletionStage<F.Either<Result, Flow<In, Out, ?>>>> f) {
      return WebSocket.acceptOrResult(inMapper, f, outMapper);
    }

    /**
     * Accept a WebSocket.
     *
     * @param f A function that takes the request header, and returns a flow to handle the WebSocket
     *     messages.
     * @return The WebSocket handler.
     */
    public WebSocket accept(Function<Http.RequestHeader, Flow<In, Out, ?>> f) {
      return acceptOrResult(
          request -> CompletableFuture.completedFuture(F.Either.Right(f.apply(request))));
    }
  }

  /**
   * Helper to create handlers for WebSockets.
   *
   * @param inMapper Function to map input messages. If it produces left, the message will be passed
   *     to the WebSocket flow, if it produces right, the message will be sent back out to the
   *     client - this can be used to send errors directly to the client.
   * @param f The function to handle the WebSocket.
   * @param outMapper Function to map output messages.
   * @return The WebSocket handler.
   */
  private static <In, Out> WebSocket acceptOrResult(
      PartialFunction<Message, F.Either<In, Message>> inMapper,
      Function<Http.RequestHeader, CompletionStage<F.Either<Result, Flow<In, Out, ?>>>> f,
      Function<Out, Message> outMapper) {
    return new WebSocket() {
      @Override
      public CompletionStage<F.Either<Result, Flow<Message, Message, ?>>> apply(
          Http.RequestHeader request) {
        return f.apply(request)
            .thenApply(
                resultOrFlow -> {
                  if (resultOrFlow.left.isPresent()) {
                    return F.Either.Left(resultOrFlow.left.get());
                  } else {
                    Flow<Message, Message, ?> flow =
                        AkkaStreams.bypassWith(
                            Flow.<Message>create().collect(inMapper),
                            play.api.libs.streams.AkkaStreams.onlyFirstCanFinishMerge(2),
                            resultOrFlow.right.get().map(outMapper::apply));
                    return F.Either.Right(flow);
                  }
                });
      }
    };
  }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package play.libs.reflect;

import java.lang.reflect.*;

/** Imported from apache.commons.lang3 3.6 */
abstract class MemberUtils {

  private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;

  /** Array of primitive number types ordered by "promotability" */
  private static final Class<?>[] ORDERED_PRIMITIVE_TYPES = {
    Byte.TYPE, Short.TYPE, Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE
  };

  /**
   * Returns whether a given set of modifiers implies package access.
   *
   * @param modifiers to test
   * @return {@code true} unless {@code package}/{@code protected}/{@code private} modifier detected
   */
  static boolean isPackageAccess(final int modifiers) {
    return (modifiers & ACCESS_TEST) == 0;
  }

  /**
   * Returns whether a {@link Member} is accessible.
   *
   * @param m Member to check
   * @return {@code true} if m is accessible
   */
  static boolean isAccessible(final Member m) {
    return m != null && Modifier.isPublic(m.getModifiers()) && !m.isSynthetic();
  }

  /**
   * XXX Default access superclass workaround.
   *
   *
When a {@code public} class has a default access superclass with {@code public} members,
   * these members are accessible. Calling them from compiled code works fine. Unfortunately, on
   * some JVMs, using reflection to invoke these members seems to (wrongly) prevent access even when
   * the modifier is {@code public}. Calling {@code setAccessible(true)} solves the problem but will
   * only work from sufficiently privileged code. Better workarounds would be gratefully accepted.
   *
   * @param o the AccessibleObject to set as accessible
   * @return a boolean indicating whether the accessibility of the object was set to true.
   */
  static boolean setAccessibleWorkaround(final AccessibleObject o) {
    if (o == null || o.isAccessible()) {
      return false;
    }
    final Member m = (Member) o;
    if (!o.isAccessible()
        && Modifier.isPublic(m.getModifiers())
        && isPackageAccess(m.getDeclaringClass().getModifiers())) {
      try {
        o.setAccessible(true);
        return true;
      } catch (final SecurityException e) { // NOPMD
        // ignore in favor of subsequent IllegalAccessException
      }
    }
    return false;
  }

  /**
   * Compares the relative fitness of two Constructors in terms of how well they match a set of
   * runtime parameter types, such that a list ordered by the results of the comparison would return
   * the best match first (least).
   *
   * @param left the "left" Constructor
   * @param right the "right" Constructor
   * @param actual the runtime parameter types to match against {@code left}/{@code right}
   * @return int consistent with {@code compare} semantics
   * @since 3.5
   */
  static int compareConstructorFit(
      final Constructor<?> left, final Constructor<?> right, final Class<?>[] actual) {
    return compareParameterTypes(Executable.of(left), Executable.of(right), actual);
  }

  /**
   * Compares the relative fitness of two Methods in terms of how well they match a set of runtime
   * parameter types, such that a list ordered by the results of the comparison would return the
   * best match first (least).
   *
   * @param left the "left" Method
   * @param right the "right" Method
   * @param actual the runtime parameter types to match against {@code left}/{@code right}
   * @return int consistent with {@code compare} semantics
   * @since 3.5
   */
  static int compareMethodFit(final Method left, final Method right, final Class<?>[] actual) {
    return compareParameterTypes(Executable.of(left), Executable.of(right), actual);
  }

  /**
   * Compares the relative fitness of two Executables in terms of how well they match a set of
   * runtime parameter types, such that a list ordered by the results of the comparison would return
   * the best match first (least).
   *
   * @param left the "left" Executable
   * @param right the "right" Executable
   * @param actual the runtime parameter types to match against {@code left}/{@code right}
   * @return int consistent with {@code compare} semantics
   */
  private static int compareParameterTypes(
      final Executable left, final Executable right, final Class<?>[] actual) {
    final float leftCost = getTotalTransformationCost(actual, left);
    final float rightCost = getTotalTransformationCost(actual, right);
    return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0;
  }

  /**
   * Gets the number of steps required to promote a primitive number to another type.
   *
   * @param srcClass the (primitive) source class
   * @param destClass the (primitive) destination class
   * @return The cost of promoting the primitive
   */
  private static float getPrimitivePromotionCost(
      final Class<?> srcClass, final Class<?> destClass) {
    float cost = 0.0f;
    Class<?> cls = srcClass;
    if (!cls.isPrimitive()) {
      // slight unwrapping penalty
      cost += 0.1f;
      cls = ClassUtils.wrapperToPrimitive(cls);
    }
    for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) {
      if (cls == ORDERED_PRIMITIVE_TYPES[i]) {
        cost += 0.1f;
        if (i < ORDERED_PRIMITIVE_TYPES.length - 1) {
          cls = ORDERED_PRIMITIVE_TYPES[i + 1];
        }
      }
    }
    return cost;
  }

  /**
   * Returns the sum of the object transformation cost for each class in the source argument list.
   *
   * @param srcArgs The source arguments
   * @param executable The executable to calculate transformation costs for
   * @return The total transformation cost
   */
  private static float getTotalTransformationCost(
      final Class<?>[] srcArgs, final Executable executable) {
    final Class<?>[] destArgs = executable.getParameterTypes();
    final boolean isVarArgs = executable.isVarArgs();

    // "source" and "destination" are the actual and declared args respectively.
    float totalCost = 0.0f;
    final long normalArgsLen = isVarArgs ? destArgs.length - 1 : destArgs.length;
    if (srcArgs.length < normalArgsLen) {
      return Float.MAX_VALUE;
    }
    for (int i = 0; i < normalArgsLen; i++) {
      totalCost += getObjectTransformationCost(srcArgs[i], destArgs[i]);
    }
    if (isVarArgs) {
      // When isVarArgs is true, srcArgs and dstArgs may differ in length.
      // There are two special cases to consider:
      final boolean noVarArgsPassed = srcArgs.length < destArgs.length;
      final boolean explicitArrayForVarags =
          srcArgs.length == destArgs.length && srcArgs[srcArgs.length - 1].isArray();

      final float varArgsCost = 0.001f;
      final Class<?> destClass = destArgs[destArgs.length - 1].getComponentType();
      if (noVarArgsPassed) {
        // When no varargs passed, the best match is the most generic matching type, not the most
        // specific.
        totalCost += getObjectTransformationCost(destClass, Object.class) + varArgsCost;
      } else if (explicitArrayForVarags) {
        final Class<?> sourceClass = srcArgs[srcArgs.length - 1].getComponentType();
        totalCost += getObjectTransformationCost(sourceClass, destClass) + varArgsCost;
      } else {
        // This is typical varargs case.
        for (int i = destArgs.length - 1; i < srcArgs.length; i++) {
          final Class<?> srcClass = srcArgs[i];
          totalCost += getObjectTransformationCost(srcClass, destClass) + varArgsCost;
        }
      }
    }
    return totalCost;
  }

  /**
   * Gets the number of steps required needed to turn the source class into the destination class.
   * This represents the number of steps in the object hierarchy graph.
   *
   * @param srcClass The source class
   * @param destClass The destination class
   * @return The cost of transforming an object
   */
  private static float getObjectTransformationCost(Class<?> srcClass, final Class<?> destClass) {
    if (destClass.isPrimitive()) {
      return getPrimitivePromotionCost(srcClass, destClass);
    }
    float cost = 0.0f;
    while (srcClass != null && !destClass.equals(srcClass)) {
      if (destClass.isInterface() && ClassUtils.isAssignable(srcClass, destClass)) {
        // slight penalty for interface match.
        // we still want an exact match to override an interface match,
        // but
        // an interface match should override anything where we have to
        // get a superclass.
        cost += 0.25f;
        break;
      }
      cost++;
      srcClass = srcClass.getSuperclass();
    }
    /*
     * If the destination class is null, we've traveled all the way up to
     * an Object match. We'll penalize this by adding 1.5 to the cost.
     */
    if (srcClass == null) {
      cost += 1.5f;
    }
    return cost;
  }

  static boolean isMatchingMethod(final Method method, final Class<?>[] parameterTypes) {
    return isMatchingExecutable(Executable.of(method), parameterTypes);
  }

  static boolean isMatchingConstructor(
      final Constructor<?> method, final Class<?>[] parameterTypes) {
    return isMatchingExecutable(Executable.of(method), parameterTypes);
  }

  private static boolean isMatchingExecutable(
      final Executable method, final Class<?>[] parameterTypes) {
    final Class<?>[] methodParameterTypes = method.getParameterTypes();
    if (method.isVarArgs()) {
      int i;
      for (i = 0; i < methodParameterTypes.length - 1 && i < parameterTypes.length; i++) {
        if (!ClassUtils.isAssignable(parameterTypes[i], methodParameterTypes[i], true)) {
          return false;
        }
      }
      final Class<?> varArgParameterType =
          methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
      for (; i < parameterTypes.length; i++) {
        if (!ClassUtils.isAssignable(parameterTypes[i], varArgParameterType, true)) {
          return false;
        }
      }
      return true;
    }
    return ClassUtils.isAssignable(parameterTypes, methodParameterTypes, true);
  }

  /**
   * A class providing a subset of the API of java.lang.reflect.Executable in Java 1.8, providing a
   * common representation for function signatures for Constructors and Methods.
   */
  private static final class Executable {
    private final Class<?>[] parameterTypes;
    private final boolean isVarArgs;

    private static Executable of(final Method method) {
      return new Executable(method);
    }

    private static Executable of(final Constructor<?> constructor) {
      return new Executable(constructor);
    }

    private Executable(final Method method) {
      parameterTypes = method.getParameterTypes();
      isVarArgs = method.isVarArgs();
    }

    private Executable(final Constructor<?> constructor) {
      parameterTypes = constructor.getParameterTypes();
      isVarArgs = constructor.isVarArgs();
    }

    public Class<?>[] getParameterTypes() {
      return parameterTypes;
    }

    public boolean isVarArgs() {
      return isVarArgs;
    }
  }
}


/*
 * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
 */

package javaguide.i18n;

import org.junit.Test;

import static java.util.stream.Collectors.joining;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import javaguide.testhelpers.MockJavaAction;
import javaguide.testhelpers.MockJavaActionHelper;
import javaguide.i18n.html.hellotemplate;
import javaguide.i18n.html.hellotemplateshort;
import play.Application;
import play.core.j.JavaHandlerComponents;
import play.mvc.Http;
import play.mvc.Result;
import play.test.WithApplication;
import static play.test.Helpers.*;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import play.i18n.Lang;
import play.i18n.Messages;
import play.i18n.MessagesApi;

import java.util.*;

public class JavaI18N extends WithApplication {

  @Override
  public Application provideApplication() {
    return fakeApplication(
        ImmutableMap.of(
            "play.i18n.langs",
            ImmutableList.of("en", "en-US", "fr"),
            "messages.path",
            "javaguide/i18n"));
  }

  @Test
  public void checkSpecifyLangHello() {
    MessagesApi messagesApi = instanceOf(MessagesApi.class);
    // #specify-lang-render
    String title = messagesApi.get(Lang.forCode("fr"), "hello");
    // #specify-lang-render

    assertTrue(title.equals("bonjour"));
  }

  @Test
  public void checkDefaultHello() {
    Result result =
        MockJavaActionHelper.call(
            new DefaultLangController(
                instanceOf(JavaHandlerComponents.class), instanceOf(MessagesApi.class)),
            fakeRequest("GET", "/"),
            mat);
    assertThat(contentAsString(result), containsString("hello"));
  }

  public static class DefaultLangController extends MockJavaAction {

    private final MessagesApi messagesApi;

    DefaultLangController(JavaHandlerComponents javaHandlerComponents, MessagesApi messagesApi) {
      super(javaHandlerComponents);
      this.messagesApi = messagesApi;
    }

    // #default-lang-render
    public Result index(Http.Request request) {
      Messages messages = this.messagesApi.preferred(request);
      return ok(hellotemplate.render(messages));
    }
    // #default-lang-render
  }

  @Test
  public void checkDefaultScalaHello() {
    Result result =
        MockJavaActionHelper.call(
            new DefaultScalaLangController(
                instanceOf(JavaHandlerComponents.class), instanceOf(MessagesApi.class)),
            fakeRequest("GET", "/"),
            mat);
    assertThat(contentAsString(result), containsString("hello"));
  }

  public static class DefaultScalaLangController extends MockJavaAction {

    private final MessagesApi messagesApi;

    DefaultScalaLangController(
        JavaHandlerComponents javaHandlerComponents, MessagesApi messagesApi) {
      super(javaHandlerComponents);
      this.messagesApi = messagesApi;
    }

    public Result index(Http.Request request) {
      Messages messages = this.messagesApi.preferred(request);
      return ok(hellotemplateshort.render(messages)); // "hello"
    }
  }

  @Test
  public void checkChangeLangHello() {
    Result result =
        MockJavaActionHelper.call(
            new ChangeLangController(
                instanceOf(JavaHandlerComponents.class), instanceOf(MessagesApi.class)),
            fakeRequest("GET", "/"),
            mat);
    assertThat(contentAsString(result), containsString("bonjour"));
  }

  @Test
  public void checkRequestMessages() {
    RequestMessagesController c = app.injector().instanceOf(RequestMessagesController.class);
    Result result = MockJavaActionHelper.call(c, fakeRequest("GET", "/"), mat);
    assertThat(contentAsString(result), containsString("hello"));
  }

  public static class ChangeLangController extends MockJavaAction {

    private final MessagesApi messagesApi;

    ChangeLangController(JavaHandlerComponents javaHandlerComponents, MessagesApi messagesApi) {
      super(javaHandlerComponents);
      this.messagesApi = messagesApi;
    }

    // #change-lang-render
    public Result index(Http.Request request) {
      Lang lang = Lang.forCode("fr");
      Messages messages = this.messagesApi.preferred(request.withTransientLang(lang));
      return ok(hellotemplate.render(messages)).withLang(lang, messagesApi);
    }
    // #change-lang-render
  }

  public static class RequestMessagesController extends MockJavaAction {

    @javax.inject.Inject
    public RequestMessagesController(JavaHandlerComponents javaHandlerComponents) {
      super(javaHandlerComponents);
    }

    @javax.inject.Inject private MessagesApi messagesApi;

    // #show-request-messages
    public Result index(Http.Request request) {
      Messages messages = this.messagesApi.preferred(request);
      String hello = messages.at("hello");
      return ok(hellotemplate.render(messages));
    }
    // #show-request-messages
  }

  @Test
  public void checkSetTransientLangHello() {
    Result result =
        MockJavaActionHelper.call(
            new SetTransientLangController(
                instanceOf(JavaHandlerComponents.class), instanceOf(MessagesApi.class)),
            fakeRequest("GET", "/"),
            mat);
    assertThat(contentAsString(result), containsString("howdy"));
  }

  public static class SetTransientLangController extends MockJavaAction {

    private final MessagesApi messagesApi;

    SetTransientLangController(
        JavaHandlerComponents javaHandlerComponents, MessagesApi messagesApi) {
      super(javaHandlerComponents);
      this.messagesApi = messagesApi;
    }

    // #set-transient-lang-render
    public Result index(Http.Request request) {
      Lang lang = Lang.forCode("en-US");
      Messages messages = this.messagesApi.preferred(request.withTransientLang(lang));
      return ok(hellotemplate.render(messages));
    }
    // #set-transient-lang-render
  }

  @Test
  public void testAcceptedLanguages() {
    Result result =
        MockJavaActionHelper.call(
            new AcceptedLanguageController(instanceOf(JavaHandlerComponents.class)),
            fakeRequest("GET", "/")
                .header("Accept-Language", "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5"),
            mat);
    assertThat(contentAsString(result), equalTo("fr-CH,fr,en,de"));
  }

  private static final class AcceptedLanguageController extends MockJavaAction {
    AcceptedLanguageController(JavaHandlerComponents javaHandlerComponents) {
      super(javaHandlerComponents);
    }

    // #accepted-languages
    public Result index(Http.Request request) {
      List<Lang> langs = request.acceptLanguages();
      String codes = langs.stream().map(Lang::code).collect(joining(","));
      return ok(codes);
    }
    // #accepted-languages
  }

  @Test
  public void testSingleApostrophe() {
    assertTrue(singleApostrophe());
  }

  private Boolean singleApostrophe() {
    MessagesApi messagesApi = app.injector().instanceOf(MessagesApi.class);
    Collection<Lang> candidates = Collections.singletonList(new Lang(Locale.US));
    Messages messages = messagesApi.preferred(candidates);
    // #single-apostrophe
    String errorMessage = messages.at("info.error");
    Boolean areEqual = errorMessage.equals("You aren't logged in!");
    // #single-apostrophe

    return areEqual;
  }

  @Test
  public void testEscapedParameters() {
    assertTrue(escapedParameters());
  }

  private Boolean escapedParameters() {
    MessagesApi messagesApi = app.injector().instanceOf(MessagesApi.class);
    Collection<Lang> candidates = Collections.singletonList(new Lang(Locale.US));
    Messages messages = messagesApi.preferred(candidates);
    // #parameter-escaping
    String errorMessage = messages.at("example.formatting");
    Boolean areEqual =
        errorMessage.equals(
            "When using MessageFormat, '{0}' is replaced with the first parameter.");
    // #parameter-escaping

    return areEqual;
  }

  // #explicit-messages-api
  private MessagesApi explicitMessagesApi() {
    return new play.i18n.MessagesApi(
        new play.api.i18n.DefaultMessagesApi(
            Collections.singletonMap(
                Lang.defaultLang().code(), Collections.singletonMap("foo", "bar")),
            new play.api.i18n.DefaultLangs().asJava()));
  }
  // #explicit-messages-api

  @Test
  public void testExplicitMessagesApi() {
    MessagesApi messagesApi = explicitMessagesApi();
    String message = messagesApi.get(Lang.defaultLang(), "foo");
    assertThat(message, equalTo("bar"));
  }
}


/*
 * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
 */

package play.i18n;

import java.util.List;

/**
 * This class implements the Messages interface.
 *
 *
This class serves two purposes. One is for backwards compatibility, it serves the old static
 * API for accessing messages. The other is a new API, which carries an inject messages, and a
 * selected language.
 *
 *
The methods for looking up messages on the old API are called get, on the new API, they are
 * called at. In Play 3.0, when we remove the old API, we may alias the at methods to the get names.
 */
public class MessagesImpl implements Messages {

  private final Lang lang;
  private final MessagesApi messagesApi;

  public MessagesImpl(Lang lang, MessagesApi messagesApi) {
    this.lang = lang;
    this.messagesApi = messagesApi;
  }

  /** @return the selected language for the messages. */
  public Lang lang() {
    return lang;
  }

  /** @return The underlying API */
  public MessagesApi messagesApi() {
    return messagesApi;
  }

  /**
   * Get the message at the given key.
   *
   *
Uses `java.text.MessageFormat` internally to format the message.
   *
   * @param key the message key
   * @param args the message arguments
   * @return the formatted message or a default rendering if the key wasn't defined
   */
  public String at(String key, Object... args) {
    return messagesApi.get(lang, key, args);
  }

  /**
   * Get the message at the first defined key.
   *
   *
Uses `java.text.MessageFormat` internally to format the message.
   *
   * @param keys the messages keys
   * @param args the message arguments
   * @return the formatted message or a default rendering if the key wasn't defined
   */
  public String at(List<String> keys, Object... args) {
    return messagesApi.get(lang, keys, args);
  }

  /**
   * Check if a message key is defined.
   *
   * @param key the message key
   * @return a Boolean
   */
  public Boolean isDefinedAt(String key) {
    return messagesApi.isDefinedAt(lang, key);
  }

  @Override
  public play.api.i18n.Messages asScala() {
    return new play.api.i18n.MessagesImpl(lang, messagesApi.asScala());
  }
}


/*
 * Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
 */

package play.db;

import java.util.Map;

import com.google.common.collect.ImmutableMap;

/** Creation helpers for manually instantiating databases. */
public final class Databases {
  // Databases is a final class and not an interface because an interface cannot be declared final.
  // Also, that should
  // clarify why the class' constructor is private: we really don't want this class to be either
  // instantiated or subclassed.
  private Databases() {}
  // ----------------
  // Creation helpers
  // ----------------

  /**
   * Create a pooled database with the given configuration.
   *
   * @param name the database name
   * @param driver the database driver class
   * @param url the database url
   * @param config a map of extra database configuration
   * @return a configured database
   */
  public static Database createFrom(
      String name, String driver, String url, Map<String, ? extends Object> config) {
    ImmutableMap.Builder<String, Object> dbConfig = new ImmutableMap.Builder<String, Object>();
    dbConfig.put("driver", driver);
    dbConfig.put("url", url);
    dbConfig.putAll(config);
    return new DefaultDatabase(name, dbConfig.build());
  }

  /**
   * Create a pooled database with the given configuration.
   *
   * @param name the database name
   * @param driver the database driver class
   * @param url the database url
   * @return a configured database
   */
  public static Database createFrom(String name, String driver, String url) {
    return createFrom(name, driver, url, ImmutableMap.<String, Object>of());
  }

  /**
   * Create a pooled database named "default" with the given configuration.
   *
   * @param driver the database driver class
   * @param url the database url
   * @param config a map of extra database configuration
   * @return a configured database
   */
  public static Database createFrom(
      String driver, String url, Map<String, ? extends Object> config) {
    return createFrom("default", driver, url, config);
  }

  /**
   * Create a pooled database named "default" with the given driver and url.
   *
   * @param driver the database driver class
   * @param url the database url
   * @return a configured database
   */
  public static Database createFrom(String driver, String url) {
    return createFrom("default", driver, url, ImmutableMap.<String, Object>of());
  }

  /**
   * Create an in-memory H2 database.
   *
   * @param name the database name
   * @param url the database url
   * @param config a map of extra database configuration
   * @return a configured in-memory h2 database
   */
  public static Database inMemory(String name, String url, Map<String, ? extends Object> config) {
    return createFrom(name, "org.h2.Driver", url, config);
  }

  /**
   * Create an in-memory H2 database.
   *
   * @param name the database name
   * @param urlOptions a map of extra url options
   * @param config a map of extra database configuration
   * @return a configured in-memory h2 database
   */
  public static Database inMemory(
      String name, Map<String, String> urlOptions, Map<String, ? extends Object> config) {
    StringBuilder urlExtra = new StringBuilder();
    for (Map.Entry<String, String> option : urlOptions.entrySet()) {
      urlExtra.append(';').append(option.getKey()).append('=').append(option.getValue());
    }
    String url = "jdbc:h2:mem:" + name + urlExtra;
    return inMemory(name, url, config);
  }

  /**
   * Create an in-memory H2 database.
   *
   * @param name the database name
   * @param config a map of extra database configuration
   * @return a configured in-memory h2 database
   */
  public static Database inMemory(String name, Map<String, ? extends Object> config) {
    return inMemory(name, "jdbc:h2:mem:" + name, config);
  }

  /**
   * Create an in-memory H2 database.
   *
   * @param name the database name
   * @return a configured in-memory h2 database
   */
  public static Database inMemory(String name) {
    return inMemory(name, ImmutableMap.<String, Object>of());
  }

  /**
   * Create an in-memory H2 database with name "default".
   *
   * @param config a map of extra database configuration
   * @return a configured in-memory h2 database
   */
  public static Database inMemory(Map<String, ? extends Object> config) {
    return inMemory("default", config);
  }

  /**
   * Create an in-memory H2 database with name "default".
   *
   * @return a configured in-memory h2 database
   */
  public static Database inMemory() {
    return inMemory("default");
  }

  /**
   * Create an in-memory H2 database with name "default" and with extra configuration provided by
   * the given entries.
   *
   * @param k1 an H2 configuration key.
   * @param v1 configuration value corresponding to `k1`
   * @return a configured in-memory H2 database
   */
  public static Database inMemoryWith(String k1, Object v1) {
    return inMemory(ImmutableMap.of(k1, v1));
  }

  /**
   * Create an in-memory H2 database with name "default" and with extra configuration provided by
   * the given entries.
   *
   * @param k1 an H2 configuration key
   * @param v1 H2 configuration value corresponding to `k1`
   * @param k2 a second H2 configuration key
   * @param v2 a configuration value corresponding to `k2`
   * @return a configured in-memory H2 database
   */
  public static Database inMemoryWith(String k1, Object v1, String k2, Object v2) {
    return inMemory(ImmutableMap.of(k1, v1, k2, v2));
  }

  /**
   * Create an in-memory H2 database with name "default" and with extra configuration provided by
   * the given entries.
   *
   * @param k1 an H2 configuration key
   * @param v1 H2 configuration value corresponding to `k1`
   * @param k2 a second H2 configuration key
   * @param v2 a configuration value corresponding to `k2`
   * @param k3 a third H2 configuration key
   * @param v3 a configuration value corresponding to `k3`
   * @return a configured in-memory H2 database
   */
  public static Database inMemoryWith(
      String k1, Object v1, String k2, Object v2, String k3, Object v3) {
    return inMemory(ImmutableMap.of(k1, v1, k2, v2, k3, v3));
  }
}


/*
 * Copyright 2003,2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.sf.cglib.beans;

import java.security.ProtectionDomain;
import java.beans.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.*;
import net.sf.cglib.core.*;
import org.objectweb.asm.ClassVisitor;

/**
 * A Map-based view of a JavaBean.  The default set of keys is the
 * union of all property names (getters or setters). An attempt to set
 * a read-only property will be ignored, and write-only properties will
 * be returned as null. Removal of objects is not a
 * supported (the key set is fixed).
 * @author Chris Nokleberg
 */
abstract public class BeanMap implements Map {
    /**
     * Limit the properties reflected in the key set of the map
     * to readable properties.
     * @see BeanMap.Generator#setRequire
     */
    public static final int REQUIRE_GETTER = 1;

    /**
     * Limit the properties reflected in the key set of the map
     * to writable properties.
     * @see BeanMap.Generator#setRequire
     */
    public static final int REQUIRE_SETTER = 2;

    /**
     * Helper method to create a new BeanMap.  For finer
     * control over the generated instance, use a new instance of
     * BeanMap.Generator instead of this static method.
     * @param bean the JavaBean underlying the map
     * @return a new BeanMap instance
     */
    public static BeanMap create(Object bean) {
        Generator gen = new Generator();
        gen.setBean(bean);
        return gen.create();
    }

    public static class Generator extends AbstractClassGenerator {
        private static final Source SOURCE = new Source(BeanMap.class.getName());

        private static final BeanMapKey KEY_FACTORY =
          (BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME);

        interface BeanMapKey {
            public Object newInstance(Class type, int require);
        }

        private Object bean;
        private Class beanClass;
        private int require;

        public Generator() {
            super(SOURCE);
        }

        /**
         * Set the bean that the generated map should reflect. The bean may be swapped
         * out for another bean of the same type using {@link #setBean}.
         * Calling this method overrides any value previously set using {@link #setBeanClass}.
         * You must call either this method or {@link #setBeanClass} before {@link #create}.
         * @param bean the initial bean
         */
        public void setBean(Object bean) {
            this.bean = bean;
            if (bean != null)
                beanClass = bean.getClass();
        }

        /**
         * Set the class of the bean that the generated map should support.
         * You must call either this method or {@link #setBeanClass} before {@link #create}.
         * @param beanClass the class of the bean
         */
        public void setBeanClass(Class beanClass) {
            this.beanClass = beanClass;
        }

        /**
         * Limit the properties reflected by the generated map.
         * @param require any combination of {@link #REQUIRE_GETTER} and
         * {@link #REQUIRE_SETTER}; default is zero (any property allowed)
         */
        public void setRequire(int require) {
            this.require = require;
        }

        protected ClassLoader getDefaultClassLoader() {
            return beanClass.getClassLoader();
        }

        protected ProtectionDomain getProtectionDomain() {
        	return ReflectUtils.getProtectionDomain(beanClass);
        }

        /**
         * Create a new instance of the BeanMap. An existing
         * generated class will be reused if possible.
         */
        public BeanMap create() {
            if (beanClass == null)
                throw new IllegalArgumentException("Class of bean unknown");
            setNamePrefix(beanClass.getName());
            return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require));
        }

        public void generateClass(ClassVisitor v) throws Exception {
            new BeanMapEmitter(v, getClassName(), beanClass, require);
        }

        protected Object firstInstance(Class type) {
            return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean);
        }

        protected Object nextInstance(Object instance) {
            return ((BeanMap)instance).newInstance(bean);
        }
    }

    /**
     * Create a new BeanMap instance using the specified bean.
     * This is faster than using the {@link #create} static method.
     * @param bean the JavaBean underlying the map
     * @return a new BeanMap instance
     */
    abstract public BeanMap newInstance(Object bean);

    /**
     * Get the type of a property.
     * @param name the name of the JavaBean property
     * @return the type of the property, or null if the property does not exist
     */
    abstract public Class getPropertyType(String name);

    protected Object bean;

    protected BeanMap() {
    }

    protected BeanMap(Object bean) {
        setBean(bean);
    }

    public Object get(Object key) {
        return get(bean, key);
    }

    public Object put(Object key, Object value) {
        return put(bean, key, value);
    }

    /**
     * Get the property of a bean. This allows a BeanMap
     * to be used statically for multiple beans--the bean instance tied to the
     * map is ignored and the bean passed to this method is used instead.
     * @param bean the bean to query; must be compatible with the type of
     * this BeanMap
     * @param key must be a String
     * @return the current value, or null if there is no matching property
     */
    abstract public Object get(Object bean, Object key);

    /**
     * Set the property of a bean. This allows a BeanMap
     * to be used statically for multiple beans--the bean instance tied to the
     * map is ignored and the bean passed to this method is used instead.
     * @param key must be a String
     * @return the old value, if there was one, or null
     */
    abstract public Object put(Object bean, Object key, Object value);

    /**
     * Change the underlying bean this map should use.
     * @param bean the new JavaBean
     * @see #getBean
     */
    public void setBean(Object bean) {
        this.bean = bean;
    }

    /**
     * Return the bean currently in use by this map.
     * @return the current JavaBean
     * @see #setBean
     */
    public Object getBean() {
        return bean;
    }

    public void clear() {
        throw new UnsupportedOperationException();
    }

    public boolean containsKey(Object key) {
        return keySet().contains(key);
    }

    public boolean containsValue(Object value) {
        for (Iterator it = keySet().iterator(); it.hasNext();) {
            Object v = get(it.next());
            if (((value == null) && (v == null)) || (value != null && value.equals(v)))
                return true;
        }
        return false;
    }

    public int size() {
        return keySet().size();
    }

    public boolean isEmpty() {
        return size() == 0;
    }

    public Object remove(Object key) {
        throw new UnsupportedOperationException();
    }

    public void putAll(Map t) {
        for (Iterator it = t.keySet().iterator(); it.hasNext();) {
            Object key = it.next();
            put(key, t.get(key));
        }
    }

    public boolean equals(Object o) {
        if (o == null || !(o instanceof Map)) {
            return false;
        }
        Map other = (Map)o;
        if (size() != other.size()) {
            return false;
        }
        for (Iterator it = keySet().iterator(); it.hasNext();) {
            Object key = it.next();
            if (!other.containsKey(key)) {
                return false;
            }
            Object v1 = get(key);
            Object v2 = other.get(key);
            if (!((v1 == null) ? v2 == null : v1.equals(v2))) {
                return false;
            }
        }
        return true;
    }

    public int hashCode() {
        int code = 0;
        for (Iterator it = keySet().iterator(); it.hasNext();) {
            Object key = it.next();
            Object value = get(key);
            code += ((key == null) ? 0 : key.hashCode()) ^
                ((value == null) ? 0 : value.hashCode());
        }
        return code;
    }

    // TODO: optimize
    public Set entrySet() {
        HashMap copy = new HashMap();
        for (Iterator it = keySet().iterator(); it.hasNext();) {
            Object key = it.next();
            copy.put(key, get(key));
        }
        return Collections.unmodifiableMap(copy).entrySet();
    }

    public Collection values() {
        Set keys = keySet();
        List values = new ArrayList(keys.size());
        for (Iterator it = keys.iterator(); it.hasNext();) {
            values.add(get(it.next()));
        }
        return Collections.unmodifiableCollection(values);
    }

    /*
     * @see java.util.AbstractMap#toString
     */
    public String toString()
    {
        StringBuffer sb = new StringBuffer();
        sb.append('{');
        for (Iterator it = keySet().iterator(); it.hasNext();) {
            Object key = it.next();
            sb.append(key);
            sb.append('=');
            sb.append(get(key));
            if (it.hasNext()) {
                sb.append(", ");
            }
        }
        sb.append('}');
        return sb.toString();
    }
}