/** * 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 org.jooby.internal; import java.lang.reflect.ParameterizedType; import java.math.BigDecimal; import java.math.BigInteger; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeParseException; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.SortedSet; import java.util.function.Function; import java.util.function.Supplier; import org.jooby.Parser; import org.jooby.Upload; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.inject.TypeLiteral; @SuppressWarnings({"unchecked", "rawtypes" }) public enum BuiltinParser implements Parser { Basic { private final Map<Class<?>, Function<String, Object>> parsers = ImmutableMap .<Class<?>, Function<String, Object>> builder() .put(BigDecimal.class, NOT_EMPTY.andThen(BigDecimal::new)) .put(BigInteger.class, NOT_EMPTY.andThen(BigInteger::new)) .put(Byte.class, NOT_EMPTY.andThen(Byte::valueOf)) .put(byte.class, NOT_EMPTY.andThen(Byte::valueOf)) .put(Double.class, NOT_EMPTY.andThen(Double::valueOf)) .put(double.class, NOT_EMPTY.andThen(Double::valueOf)) .put(Float.class, NOT_EMPTY.andThen(Float::valueOf)) .put(float.class, NOT_EMPTY.andThen(Float::valueOf)) .put(Integer.class, NOT_EMPTY.andThen(Integer::valueOf)) .put(int.class, NOT_EMPTY.andThen(Integer::valueOf)) .put(Long.class, NOT_EMPTY.andThen(this::toLong)) .put(long.class, NOT_EMPTY.andThen(this::toLong)) .put(Short.class, NOT_EMPTY.andThen(Short::valueOf)) .put(short.class, NOT_EMPTY.andThen(Short::valueOf)) .put(Boolean.class, NOT_EMPTY.andThen(this::toBoolean)) .put(boolean.class, NOT_EMPTY.andThen(this::toBoolean)) .put(Character.class, NOT_EMPTY.andThen(this::toCharacter)) .put(char.class, NOT_EMPTY.andThen(this::toCharacter)) .put(String.class, this::toString) .build(); @Override public Object parse(final TypeLiteral<?> type, final Parser.Context ctx) throws Throwable { Function<String, Object> parser = parsers.get(type.getRawType()); if (parser != null) { return ctx .param(values -> parser.apply(values.get(0))).body(body -> parser.apply(body.text())); } return ctx.next(); } private String toString(final String value) { return value; } private char toCharacter(final String value) { return value.charAt(0); } private Boolean toBoolean(final String value) { if ("true".equals(value)) { return Boolean.TRUE; } else if ("false".equals(value)) { return Boolean.FALSE; } throw new IllegalArgumentException("Not a boolean: " + value); } private Long toLong(final String value) { try { return Long.valueOf(value); } catch (NumberFormatException ex) { // long as date, like If-Modified-Since try { LocalDateTime date = LocalDateTime.parse(value, Headers.fmt); Instant instant = date.toInstant(ZoneOffset.UTC); return instant.toEpochMilli(); } catch (DateTimeParseException ignored) { throw ex; } } } }, Collection { private final Map<Class<?>, Supplier<ImmutableCollection.Builder<?>>> parsers = ImmutableMap.<Class<?>, Supplier<ImmutableCollection.Builder<?>>> builder() .put(List.class, ImmutableList.Builder::new) .put(Set.class, ImmutableSet.Builder::new) .put(SortedSet.class, ImmutableSortedSet::naturalOrder) .build(); private boolean matches(final TypeLiteral<?> toType) { return parsers.containsKey(toType.getRawType()) && toType.getType() instanceof ParameterizedType; } @Override public Object parse(final TypeLiteral<?> type, final Parser.Context ctx) throws Throwable { if (matches(type)) { return ctx.param(values -> { ImmutableCollection.Builder builder = parsers.get(type.getRawType()).get(); TypeLiteral<?> paramType = TypeLiteral.get(((ParameterizedType) type.getType()) .getActualTypeArguments()[0]); for (Object value : values) { builder.add(ctx.next(paramType, value)); } return builder.build(); }).upload(uploads -> { ImmutableCollection.Builder builder = parsers.get(type.getRawType()).get(); TypeLiteral<Upload> paramType = TypeLiteral.get(Upload.class); for (Upload upload : uploads) { builder.add(ctx.next(paramType, upload)); } return builder.build(); }); } else { return ctx.next(); } } }, Optional { private boolean matches(final TypeLiteral<?> toType) { return Optional.class == toType.getRawType() && toType.getType() instanceof ParameterizedType; } @Override public Object parse(final TypeLiteral<?> type, final Parser.Context ctx) throws Throwable { if (matches(type)) { TypeLiteral<?> paramType = TypeLiteral.get(((ParameterizedType) type.getType()) .getActualTypeArguments()[0]); return ctx .param(values -> { if (values.size() == 0) { return java.util.Optional.empty(); } return java.util.Optional.of(ctx.next(paramType)); }).body(body -> { if (body.length() == 0) { return java.util.Optional.empty(); } return java.util.Optional.of(ctx.next(paramType)); }).upload(files -> { return java.util.Optional.of(ctx.next(paramType)); }); } else { return ctx.next(); } } }, Enum { @Override public Object parse(final TypeLiteral<?> type, final Parser.Context ctx) throws Throwable { Class rawType = type.getRawType(); if (Enum.class.isAssignableFrom(rawType)) { return ctx .param(values -> toEnum(rawType, values.get(0))) .body(body -> toEnum(rawType, body.text())); } else { return ctx.next(); } } Object toEnum(final Class type, final String value) { Set<Enum> set = EnumSet.allOf(type); return set.stream() .filter(e -> e.name().equalsIgnoreCase(value)) .findFirst() .orElseGet(() -> java.lang.Enum.valueOf(type, value)); } }, Upload { @Override public Object parse(final TypeLiteral<?> type, final Context ctx) throws Throwable { if (Upload.class == type.getRawType()) { return ctx.upload(uploads -> uploads.get(0)); } else { return ctx.next(); } } }, Bytes { @Override public Object parse(final TypeLiteral<?> type, final Parser.Context ctx) throws Throwable { if (type.getRawType() == byte[].class) { return ctx.body(body -> body.bytes()); } return ctx.next(); } @Override public String toString() { return "byte[]"; } } }