001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.functions.conversion;
020
021import java.util.Iterator;
022
023import lucee.commons.lang.StringUtil;
024import lucee.runtime.PageContext;
025import lucee.runtime.exp.FunctionException;
026import lucee.runtime.exp.PageException;
027import lucee.runtime.ext.function.Function;
028import lucee.runtime.functions.BIF;
029import lucee.runtime.interpreter.JSONExpressionInterpreter;
030import lucee.runtime.op.Caster;
031import lucee.runtime.type.Array;
032import lucee.runtime.type.ArrayImpl;
033import lucee.runtime.type.Collection;
034import lucee.runtime.type.Collection.Key;
035import lucee.runtime.type.KeyImpl;
036import lucee.runtime.type.QueryImpl;
037import lucee.runtime.type.Struct;
038import lucee.runtime.type.util.CollectionUtil;
039import lucee.runtime.type.util.KeyConstants;
040import lucee.runtime.type.util.ListUtil;
041
042/**
043 * Decodes Binary Data that are encoded as String
044 */
045public final class DeserializeJSON extends BIF implements Function {
046
047        private static final Key ROWCOUNT = KeyImpl.intern("ROWCOUNT");
048
049        public static Object call(PageContext pc, String JSONVar) throws PageException {
050            return call(pc,JSONVar,true);
051        }
052        public static Object call(PageContext pc, String JSONVar,boolean strictMapping) throws PageException {
053                Object result = new JSONExpressionInterpreter().interpret(pc, JSONVar);
054                if(!strictMapping) return toQuery(result);
055                return result;
056        }
057
058    @Override
059    public Object invoke(PageContext pc, Object[] args) throws PageException {
060        if (args.length == 1) {
061            return call(pc, Caster.toString(args[0]));
062        } else if (args.length == 2) {
063            return call(pc, Caster.toString(args[0]), Caster.toBoolean(args[1]));
064        }
065        throw new FunctionException(pc, "deserializeJSON", 0, 1, args.length - 1);
066    }
067
068    //{"COLUMNS":["AAA","BBB"],"DATA":[["a","b"],["c","d"]]}
069        //{"ROWCOUNT":2,"COLUMNS":["AAA","BBB"],"DATA":{"aaa":["a","c"],"bbb":["b","d"]}}
070        private static Object toQuery(Object obj) throws PageException {
071                if(obj instanceof Struct) {
072                        Struct sct=(Struct) obj;
073                        Key[] keys = CollectionUtil.keys(sct);
074                        
075                        // Columns
076                        Key[] columns = null;
077                        if(contains(keys,KeyConstants._COLUMNS)) 
078                                columns = toColumns(sct.get(KeyConstants._COLUMNS,null));
079                        else if(contains(keys,KeyConstants._COLUMNLIST)) 
080                                columns = toColumnlist(sct.get(KeyConstants._COLUMNLIST,null));
081                        
082                        // rowcount
083                        int rowcount = -1;
084                        if(contains(keys,ROWCOUNT))
085                                rowcount = toRowCount(sct.get(ROWCOUNT,null));
086                        else if(contains(keys,KeyConstants._RECORDCOUNT))
087                                rowcount = toRowCount(sct.get(KeyConstants._RECORDCOUNT,null));
088                                
089                        
090                        if(columns!=null) {
091                                if(keys.length==2 && contains(keys,KeyConstants._DATA)) {
092                                        
093                                        Array[] data = toData(sct.get(KeyConstants._DATA,null),columns);
094                                        if(data!=null) {
095                                                return new QueryImpl(columns,data,"query");
096                                        }
097                                }
098                                
099                                else if(keys.length==3 && rowcount!=-1 && contains(keys,KeyConstants._DATA)) {
100                                        Array[] data = toData(sct.get(KeyConstants._DATA,null),columns,rowcount);
101                                        if(data!=null) {
102                                                return new QueryImpl(columns,data,"query");
103                                        }
104                                }
105                        }
106                        return toQuery(sct,keys);
107                }
108                /*else if(obj instanceof Query) {
109                        return toQuery((Query) obj);
110                }*/     
111                else if(obj instanceof Collection) {
112                        Collection coll=(Collection) obj;
113                        return toQuery(coll, CollectionUtil.keys(coll));
114                }
115                
116                return obj;
117                
118        }
119
120        /*private static Object toQuery(Query qry) throws DatabaseException {
121                int rows=qry.getRecordcount();
122                String[] columns = qry.getColumns();
123                Object src,trg;
124                for(int row=1;row<=rows;row++) {
125                        for(int col=0;col<columns.length;col++) {
126                                trg=toQuery(src=qry.getAt(columns[col], row, null));
127                                if(src!=trg) qry.setAtEL(columns[col], row, trg);
128                        }
129                }
130                return qry;
131        }*/
132        
133        private static Collection toQuery(Collection coll, Key[] keys) throws PageException {
134                Object src,trg;
135                for(int i=0;i<keys.length;i++) {
136                        trg=toQuery(src=coll.get(keys[i],null));
137                        if(src!=trg) coll.setEL(keys[i], trg);
138                }
139                return coll;
140        }
141        
142        private static int toRowCount(Object obj) {
143                return Caster.toIntValue(obj,-1);
144        }
145
146        private static Array[] toData(Object obj, Key[] columns, int rowcount) throws PageException {
147                if(columns==null || rowcount==-1) return null;
148
149                Struct sct = Caster.toStruct(obj,null,false);
150                if(sct!=null && sct.size()==columns.length) {
151                        Array[] datas=new Array[columns.length];
152                        Array col;
153                        int colLen=-1;
154                        for(int i=0;i<columns.length;i++) {
155                                col=Caster.toArray(sct.get(columns[i],null),null);
156                                if(col==null || colLen!=-1 && colLen!=col.size()) return null;
157                                datas[i]=(Array) toQuery(col,CollectionUtil.keys(col));
158                                colLen=col.size();
159                        }
160                        return datas;
161                }
162                return null;
163        }
164        
165        private static Array[] toData(Object obj,Key[] columns) throws PageException {
166                if(columns==null) return null;
167                
168                Array arr = Caster.toArray(obj,null);
169                if(arr!=null) {
170                        Array[] datas=new Array[columns.length];
171                        for(int i=0;i<datas.length;i++) {
172                                datas[i]=new ArrayImpl();
173                        }
174                        
175                        Array data;
176                        Iterator<Object> it = arr.valueIterator();
177                        while(it.hasNext()) {
178                                data=Caster.toArray(it.next(),null);
179                                if(data==null || data.size()!=datas.length) return null;
180                                for(int i=0;i<datas.length;i++) {
181                                        datas[i].appendEL(toQuery(data.get(i+1,null)));
182                                }
183                        }
184                        return datas;
185                }
186                return null;
187        }
188
189        
190        private static Key[] toColumns(Object obj) {
191                Array arr = Caster.toArray(obj,null);
192                if(arr!=null) {
193                        Key[] columns=new Key[arr.size()];
194                        String column;
195                        int index=0;
196                        Iterator<Object> it = arr.valueIterator();
197                        while(it.hasNext()) {
198                                column=Caster.toString(it.next(),null);
199                                if(StringUtil.isEmpty(column)) return null;
200                                columns[index++]=KeyImpl.getInstance(column);
201                        }
202                        return columns;
203                }
204                return null;
205        }
206        
207        private static Key[] toColumnlist(Object obj) throws PageException {
208                String list = Caster.toString(obj,null);
209                if(StringUtil.isEmpty(list)) return null;
210                return toColumns(ListUtil.trimItems(ListUtil.listToArrayRemoveEmpty(list, ',')));
211        }
212        
213        
214        /*private static boolean contains(Key[] haystack, Key[] needle) {
215                Key h;
216                outer:for(int i=0;i<haystack.length;i++) {
217                        h=haystack[i];
218                        for(int y=0;y<needle.length;y++) {
219                                if(h.equalsIgnoreCase(needle[y])) continue outer;
220                        }
221                        return false;
222                }
223                return true;
224        }*/
225        
226        
227        private static boolean contains(Key[] haystack, Key needle) {
228                for(int i=0;i<haystack.length;i++) {
229                        if(haystack[i].equalsIgnoreCase(needle)) return true;
230                }
231                return false;
232        }
233}