001/* 
002 * JKNIV, SQLegance keeping queries maintainable.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.sqlegance.params;
021
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029import org.apache.commons.beanutils.PropertyUtils;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import net.sf.jkniv.reflect.BasicType;
034
035/**
036 * 
037 * @author Alisson Gomes
038 * @since 0.6.0
039 */
040public abstract class AbstractParamParser implements ParamParser
041{
042    protected final Logger         log                 = LoggerFactory.getLogger(getClass());
043    // find the pattern 'a-z..0..9()*'
044    protected static final String  REGEX_SINGLE_QUOTE  = "|'[^']*')+"; //"\\?";
045    
046    // ":in:[\w\.?]"
047    protected static final String  REGEX_IN            = ":in:[\\w\\.?]+"; //FIXED bug :a.property.value -> :a
048    
049    // find the pattern #{id}
050    protected static final String  REGEX_HASH_MARK     = "(" + "#\\{[\\w\\.?]+\\}" +  // #{id}
051                                                            "|" + REGEX_IN + REGEX_SINGLE_QUOTE;
052    
053    // find the pattern :id
054    protected static final String  REGEX_COLON_MARK    = "(" + ":[\\w\\.?]+" + //FIXED bug :a.property.value -> :a
055                                                             "|" + REGEX_IN + REGEX_SINGLE_QUOTE;
056
057    // find the pattern $id
058    protected static final String  REGEX_DOLLAR_MARK    = "(" + "\\$[\\w\\.?]+" + //FIXED bug $a.property.value -> $a
059            "|" + REGEX_IN + REGEX_SINGLE_QUOTE;
060    
061    //":[\\w]+";// (:[\w]+|'[^']*')+  FIXED nao pode estar dentro de aspas 'YYYY-MM-DD HH24:MI:SS'
062    // find the pattern ?
063    protected static final String  REGEX_QUESTION_MARK = "(" + "[\\?]+" + "|" + REGEX_IN + REGEX_SINGLE_QUOTE;    //"\\?";
064    
065    //private static final Pattern PATTERN_HASH          = Pattern.compile(REGEX_HASH_SYMBOL,     Pattern.CASE_INSENSITIVE);
066    //private static final Pattern PATTERN_TWO_DOTS      = Pattern.compile(REGEX_TWODOTS_SYMBOL,  Pattern.CASE_INSENSITIVE);
067    //private static final Pattern PATTERN_QUESTION      = Pattern.compile(REGEX_QUESTION_SYMBOL, Pattern.CASE_INSENSITIVE);
068    
069    private static final Pattern PATTERN_IN          = Pattern.compile(REGEX_IN, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
070    private static final BasicType basicType           = BasicType.getInstance();
071    
072    abstract Pattern getPatternParams();
073    
074//    public AbstractParamParser(String regex)
075//    {
076//        this.PATTERN_PARAMS = Pattern.compile(regex, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
077//    }
078    
079    @Override
080    public String replaceForPlaceholder(String query)
081    {
082        return replaceForPlaceholder(query, Collections.emptyList());
083    }
084
085    @Override
086    public String replaceForPlaceholderWithNumber(String query, Object params)
087    {
088        StringBuffer sb = new StringBuffer(query);
089        Matcher matcherTwoDots = getPatternParams().matcher(query);
090        Map<String, String> mapForINClauseParams = new HashMap<String, String>();
091        int startIndex = 0;
092        int endIndex = 0;
093        int index = 1;
094        while (matcherTwoDots.find())
095        {
096            String match = matcherTwoDots.group();
097            if (match.startsWith("'"))
098                continue;
099            if (match.startsWith(":in:"))
100            {
101                String paramName = match.substring(4, match.length());
102                Object[] paramsAsArray = getParamsClauseIN(params, paramName);
103                if (paramsAsArray != null && paramsAsArray.length > 0)
104                {
105                    StringBuilder tmp = new StringBuilder();
106                    for(int i=0; i<paramsAsArray.length; i++)
107                        tmp.append( i>0? "," : "")
108                           .append(getPlaceholder()+index++);
109                    
110                    mapForINClauseParams.put(match, tmp.toString());
111                }
112            }
113            else
114            {
115                startIndex = matcherTwoDots.start();
116                endIndex = matcherTwoDots.end();
117                sb.replace(startIndex, endIndex, padspace(endIndex - startIndex, index++));
118            }
119        }
120        if (!mapForINClauseParams.isEmpty())
121        {
122            String newSql = sb.toString();
123            for(String key : mapForINClauseParams.keySet())
124                newSql = newSql.replaceAll(key, mapForINClauseParams.get(key));
125            
126            sb = new StringBuffer(newSql);
127        }
128        return sb.toString();
129    }    
130
131    @Override
132    public String replaceForPlaceholder(String query, Object params)
133    {
134        StringBuffer sb = new StringBuffer(query);
135        Matcher matcherTwoDots = getPatternParams().matcher(query);
136        Map<String, String> mapForINClauseParams = new HashMap<String, String>();
137        int startIndex = 0;
138        int endIndex = 0;
139        while (matcherTwoDots.find())
140        {
141            String match = matcherTwoDots.group();
142            if (match.startsWith("'"))
143                continue;
144            if (match.startsWith(":in:"))
145            {
146                String paramName = match.substring(4, match.length());
147                Object[] paramsAsArray = getParamsClauseIN(params, paramName);
148                if (paramsAsArray!=null && paramsAsArray.length > 0)
149                {
150                    StringBuilder tmp = new StringBuilder();
151                    for(int i=0; i<paramsAsArray.length; i++)
152                        tmp.append( i>0? "," : "")
153                           .append(getPlaceholder());
154                    
155                    mapForINClauseParams.put(match, tmp.toString());
156                }
157            }
158            else
159            {
160                startIndex = matcherTwoDots.start();
161                endIndex = matcherTwoDots.end();
162                sb.replace(startIndex, endIndex, padspace(endIndex - startIndex, -1));
163            }
164        }
165        if (!mapForINClauseParams.isEmpty())
166        {
167            String newSql = sb.toString();
168            for(String key : mapForINClauseParams.keySet())
169                newSql = newSql.replaceAll(key, mapForINClauseParams.get(key));
170            
171            sb = new StringBuffer(newSql);
172        }
173        return sb.toString();
174    }    
175
176    @Override
177    public String getPlaceholder()
178    {
179        return "?";
180    }
181    
182    protected String padspace(int size, int index)
183    {
184        StringBuffer s = new StringBuffer(getPlaceholder() +(index < 0 ? "" : index));
185        int newSize = (index < 0 ? size : size - String.valueOf(index).length());// discount the length string from index
186        for (int i = 1; i < newSize; i++)
187            s.append(" ");
188        return s.toString();
189    }
190    
191    protected Object[] getParamsClauseIN(Object params, String name)
192    {
193        Object[] paramsClauseIN = getParamsAsArray(params);
194        if (paramsClauseIN == null)
195        {
196            try
197            {
198                Object value = PropertyUtils.getProperty(params, name);
199                if (value != null && (value instanceof String || basicType.isNumberType(value.getClass())))
200                    paramsClauseIN = new Object[]{ value };
201                else
202                    paramsClauseIN = getParamsAsArray(value);
203            }
204            catch (Exception e)//IllegalAccessException, InvocationTargetException, NoSuchMethodException
205            {
206                log.warn("Cannot read property [{}] for object [{}]. cause={}", name,
207                        (params == null ? "null" : params.toString()), e.getMessage());
208            }
209        }
210        return paramsClauseIN;
211    }
212    
213    private Object[] getParamsAsArray(Object params)
214    {
215        Object[] array = null;
216        if (params instanceof Collection)
217            array = ((Collection<?>) params).toArray();
218        else if (params != null && params.getClass().isArray())
219        {
220            array = (Object[]) params;
221        }
222        return array;
223    }
224    
225}