/* * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team) * * 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 com.querydsl.core.types; import java.math.BigDecimal; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.querydsl.core.types.Template.Element; /** * {@code TemplateFactory} is a factory for {@link Template} instances * * @author tiwe * */ public class TemplateFactory { private static final Map<String, Operator> OPERATORS = ImmutableMap.<String, Operator>of( "+", Ops.ADD, "-", Ops.SUB, "*", Ops.MULT, "/", Ops.DIV); public static final TemplateFactory DEFAULT = new TemplateFactory('\\'); private static final Constant<String> PERCENT = ConstantImpl.create("%"); private static final Pattern elementPattern = Pattern.compile("\\{" + "(%?%?)" + "(\\d+)" + "(?:([+-/*])(?:(\\d+)|'(-?\\d+(?:\\.\\d+)?)'))?" + "([slu%]?%?)" + "\\}"); private final Map<String,Template> cache = new ConcurrentHashMap<String,Template>(); private final char escape; private final Function<Object,Object> toLowerCase = new Function<Object,Object>() { @Override public Object apply(Object arg) { if (arg instanceof Constant<?>) { return ConstantImpl.create(apply(arg.toString()).toString()); } else if (arg instanceof Expression) { return ExpressionUtils.operation(String.class, Ops.LOWER, (Expression) arg); } else { return String.valueOf(arg).toLowerCase(); } } }; private final Function<Object,Object> toUpperCase = new Function<Object,Object>() { @Override public Object apply(Object arg) { if (arg instanceof Constant<?>) { return ConstantImpl.create(apply(arg.toString()).toString()); } else if (arg instanceof Expression) { return ExpressionUtils.operation(String.class, Ops.UPPER, (Expression) arg); } else { return String.valueOf(arg).toUpperCase(); } } }; private final Function<Object,Object> toStartsWithViaLike = new Function<Object,Object>() { @Override public Object apply(Object arg) { if (arg instanceof Constant<?>) { return ConstantImpl.create(apply(arg.toString()).toString()); } else if (arg instanceof Expression) { return ExpressionUtils.operation(String.class, Ops.CONCAT, (Expression) arg, PERCENT); } else { return escapeForLike(String.valueOf(arg)) + "%"; } } }; private final Function<Object,Object> toStartsWithViaLikeLower = new Function<Object,Object>() { @Override public Object apply(Object arg) { if (arg instanceof Constant<?>) { return ConstantImpl.create(apply(arg.toString()).toString()); } else if (arg instanceof Expression) { Expression<String> concatenated = ExpressionUtils.operation(String.class, Ops.CONCAT, (Expression) arg, PERCENT); return ExpressionUtils.operation(String.class, Ops.LOWER, concatenated); } else { return escapeForLike(String.valueOf(arg).toLowerCase()) + "%"; } } }; private final Function<Object,Object> toEndsWithViaLike = new Function<Object,Object>() { @Override public Object apply(Object arg) { if (arg instanceof Constant) { return ConstantImpl.create(apply(arg.toString()).toString()); } else if (arg instanceof Expression) { return ExpressionUtils.operation(String.class, Ops.CONCAT, PERCENT, (Expression) arg); } else { return "%" + escapeForLike(String.valueOf(arg)); } } }; private final Function<Object,Object> toEndsWithViaLikeLower = new Function<Object,Object>() { @Override public Object apply(Object arg) { if (arg instanceof Constant<?>) { return ConstantImpl.create(apply(arg.toString()).toString()); } else if (arg instanceof Expression) { Expression<String> concatenated = ExpressionUtils.operation(String.class, Ops.CONCAT, PERCENT, (Expression) arg); return ExpressionUtils.operation(String.class, Ops.LOWER, concatenated); } else { return "%" + escapeForLike(String.valueOf(arg).toLowerCase()); } } }; private final Function<Object,Object> toContainsViaLike = new Function<Object,Object>() { @Override public Object apply(Object arg) { if (arg instanceof Constant<?>) { return ConstantImpl.create(apply(arg.toString()).toString()); } else if (arg instanceof Expression) { Expression<String> concatenated = ExpressionUtils.operation(String.class, Ops.CONCAT, PERCENT, (Expression) arg); return ExpressionUtils.operation(String.class, Ops.CONCAT, concatenated, PERCENT); } else { return "%" + escapeForLike(String.valueOf(arg)) + "%"; } } }; private final Function<Object,Object> toContainsViaLikeLower = new Function<Object,Object>() { @Override public Object apply(Object arg) { if (arg instanceof Constant<?>) { return ConstantImpl.create(apply(arg.toString()).toString()); } else if (arg instanceof Expression) { Expression<String> concatenated = ExpressionUtils.operation(String.class, Ops.CONCAT, PERCENT, (Expression) arg); concatenated = ExpressionUtils.operation(String.class, Ops.CONCAT, concatenated, PERCENT); return ExpressionUtils.operation(String.class, Ops.LOWER, concatenated); } else { return "%" + escapeForLike(String.valueOf(arg).toLowerCase()) + "%"; } } }; public TemplateFactory(char escape) { this.escape = escape; } public Template create(String template) { if (cache.containsKey(template)) { return cache.get(template); } else { Matcher m = elementPattern.matcher(template); final ImmutableList.Builder<Element> elements = ImmutableList.builder(); int end = 0; while (m.find()) { if (m.start() > end) { elements.add(new Template.StaticText(template.substring(end, m.start()))); } String premodifiers = m.group(1).toLowerCase(Locale.ENGLISH); int index = Integer.parseInt(m.group(2)); String postmodifiers = m.group(6).toLowerCase(Locale.ENGLISH); boolean asString = false; Function<Object, Object> transformer = null; switch (premodifiers.length()) { case 1: transformer = toEndsWithViaLike; break; case 2: transformer = toEndsWithViaLikeLower; break; } switch (postmodifiers.length()) { case 1: switch (postmodifiers.charAt(0)) { case '%': if (transformer == null) { transformer = toStartsWithViaLike; } else { transformer = toContainsViaLike; } break; case 'l': transformer = toLowerCase; break; case 'u': transformer = toUpperCase; break; case 's': asString = true; break; } break; case 2: if (transformer == null) { transformer = toStartsWithViaLikeLower; } else { transformer = toContainsViaLikeLower; } break; } if (m.group(4) != null) { Operator operator = OPERATORS.get(m.group(3)); int index2 = Integer.parseInt(m.group(4)); elements.add(new Template.Operation(index, index2, operator, asString)); } else if (m.group(5) != null) { Operator operator = OPERATORS.get(m.group(3)); Number number; if (m.group(5).contains(".")) { number = new BigDecimal(m.group(5)); } else { number = Integer.valueOf(m.group(5)); } elements.add(new Template.OperationConst(index, number, operator, asString)); } else if (asString) { elements.add(new Template.AsString(index)); } else if (transformer != null) { elements.add(new Template.Transformed(index, transformer)); } else { elements.add(new Template.ByIndex(index)); } end = m.end(); } if (end < template.length()) { elements.add(new Template.StaticText(template.substring(end))); } Template rv = new Template(template, elements.build()); cache.put(template, rv); return rv; } } public String escapeForLike(String str) { final StringBuilder rv = new StringBuilder(str.length() + 3); for (int i = 0; i < str.length(); i++) { final char ch = str.charAt(i); if (ch == escape || ch == '%' || ch == '_') { rv.append(escape); } rv.append(ch); } return rv.toString(); } }