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.type;
020
021import java.io.Externalizable;
022import java.io.IOException;
023import java.io.ObjectInput;
024import java.io.ObjectOutput;
025import java.util.Date;
026
027import lucee.commons.digest.WangJenkins;
028import lucee.commons.lang.SizeOf;
029import lucee.commons.lang.StringUtil;
030import lucee.runtime.exp.CasterException;
031import lucee.runtime.exp.PageException;
032import lucee.runtime.op.Castable;
033import lucee.runtime.op.Caster;
034import lucee.runtime.op.Operator;
035import lucee.runtime.op.date.DateCaster;
036import lucee.runtime.type.Collection.Key;
037import lucee.runtime.type.dt.DateTime;
038
039public class KeyImpl implements Collection.Key,Castable,Comparable,Sizeable,Externalizable,WangJenkins {
040
041        private static final long serialVersionUID = -8864844181140115609L; // do not change
042
043        
044        private static final long[] byteTable = createLookupTable();
045        private static final long HSTART = 0xBB40E64DA205B064L;
046        private static final long HMULT = 7664345821815920749L;
047        
048        //private boolean intern;
049        private String key;
050        private transient String lcKey;
051        private transient String ucKey;
052        private transient int wjh;
053        private transient int sfm=-1;
054        private transient long h64;
055        
056        public KeyImpl() {
057                // DO NOT USE, JUST FOR UNSERIALIZE
058        }
059        
060        private static final long[] createLookupTable() {
061                long[] _byteTable = new long[256];
062                long h = 0x544B2FBACAAF1684L;
063                for (int i = 0; i < 256; i++) {
064                        for (int j = 0; j < 31; j++) {
065                                h = (h >>> 7) ^ h;
066                                h = (h << 11) ^ h;
067                                h = (h >>> 10) ^ h;
068                        }
069                        _byteTable[i] = h;
070                }
071                return _byteTable;
072        }
073
074        private static final long createHash64(CharSequence cs) {
075                long h = HSTART;
076                final long hmult = HMULT;
077                final long[] ht = byteTable;
078                final int len = cs.length();
079                for (int i = 0; i < len; i++) {
080                        char ch = cs.charAt(i);
081                        h = (h * hmult) ^ ht[ch & 0xff];
082                        h = (h * hmult) ^ ht[(ch >>> 8) & 0xff];
083                }
084                return h;
085        }
086        
087        @Override
088        public int wangJenkinsHash() {
089                if(wjh==0) {
090                        int h = hashCode();
091                        h += (h <<  15) ^ 0xffffcd7d;
092                h ^= (h >>> 10);
093                h += (h <<   3);
094                h ^= (h >>>  6);
095                h += (h <<   2) + (h << 14);
096                wjh= h ^ (h >>> 16);
097                }
098                return wjh;
099        }
100        
101        public int slotForMap() {
102                if(sfm == -1) {
103                int h = 0;
104                h ^= hashCode();
105                h ^= (h >>> 20) ^ (h >>> 12);
106                sfm = h ^ (h >>> 7) ^ (h >>> 4);
107                }
108                return sfm;
109    }
110
111        public void writeExternal(ObjectOutput out) throws IOException {
112                out.writeObject(key);
113        }
114
115        public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
116                key=(String) in.readObject();
117                ucKey=key.toUpperCase();
118                h64=createHash64(ucKey);        
119        }
120
121        public KeyImpl(String key) {
122                this.key=key;
123                this.ucKey=key.toUpperCase();
124                h64=createHash64(ucKey);
125        }
126
127        /**
128         * for dynamic loading of key objects
129         * @param string
130         * @return
131         */
132        public static Collection.Key init(String key) {
133                return new KeyImpl(key);
134        }
135
136        public static Collection.Key _const(String key) {
137                return new KeyImpl(key);
138        }
139
140        public static Collection.Key getInstance(String key) {
141                return new KeyImpl(key);
142        }
143
144        public static Collection.Key intern(String key) {
145                return new KeyImpl(key);
146        }
147
148        @Override
149        public char charAt(int index) {
150                return key.charAt(index);
151        }
152
153        @Override
154        public char lowerCharAt(int index) {
155                return getLowerString().charAt(index);
156        }
157        
158        public char upperCharAt(int index) {
159                return ucKey.charAt(index);
160        }
161
162        @Override
163        public String getLowerString() {
164                if(lcKey==null)lcKey=StringUtil.toLowerCase(key);
165                return lcKey;
166        }
167        
168        public String getUpperString() {
169                return ucKey;
170        }
171
172        @Override
173        public String toString() {
174                return key;
175        }
176
177        @Override
178        public String getString() {
179                return key;
180        }
181
182        @Override
183        public boolean equals(Object other) {
184                if(this==other) return true;
185                if(other instanceof KeyImpl)    {
186                        return hash()==((KeyImpl)other).hash();
187                }
188                if(other instanceof String)     {
189                        return key.equalsIgnoreCase((String)other);
190                }
191                if(other instanceof Key)        {
192                        return ucKey.equalsIgnoreCase(((Key)other).getUpperString());
193                }
194                return false;
195        }
196
197
198        @Override
199        public boolean equalsIgnoreCase(Key other) {
200                if(this==other) return true;
201                if(other instanceof KeyImpl)    {
202                        return h64==((KeyImpl)other).h64;//return lcKey.equals((((KeyImpl)other).lcKey));
203                }
204                return ucKey.equalsIgnoreCase(other.getLowerString());
205        }
206
207        @Override
208        public int hashCode() {
209                return ucKey.hashCode();
210        }
211        
212        // FUTURE add to interface
213        public long hash() {
214                return h64;
215        }
216
217        @Override
218        public int getId() {// set to deprecated, use instead hash()
219                return hashCode();
220        }
221
222        @Override
223        public boolean castToBooleanValue() throws PageException {
224                return Caster.toBooleanValue(key);
225        }
226    
227    @Override
228    public Boolean castToBoolean(Boolean defaultValue) {
229        return Caster.toBoolean(key,defaultValue);
230    }
231
232        @Override
233        public DateTime castToDateTime() throws PageException {
234                return Caster.toDatetime(key,null);
235        }
236    
237    @Override
238    public DateTime castToDateTime(DateTime defaultValue) {
239        return DateCaster.toDateAdvanced(key,DateCaster.CONVERTING_TYPE_OFFSET,null,defaultValue);
240    }
241
242        @Override
243        public double castToDoubleValue() throws PageException {
244                return Caster.toDoubleValue(key);
245        }
246    
247    @Override
248    public double castToDoubleValue(double defaultValue) {
249        return Caster.toDoubleValue(key,defaultValue);
250    }
251
252        @Override
253        public String castToString() throws PageException {
254                return key;
255        }
256
257        @Override
258        public String castToString(String defaultValue) {
259                return key;
260        }
261
262        @Override
263        public int compareTo(boolean b) throws PageException {
264                return Operator.compare(key, b);
265        }
266
267        @Override
268        public int compareTo(DateTime dt) throws PageException {
269                return Operator.compare(key, (Date)dt);
270        }
271
272        @Override
273        public int compareTo(double d) throws PageException {
274                return Operator.compare(key, d);
275        }
276
277        @Override
278        public int compareTo(String str) throws PageException {
279                return Operator.compare(key, str);
280        }
281        
282
283        public int compareTo(Object o) {
284                try {
285                        return Operator.compare(key, o);
286                } catch (PageException e) {
287                        ClassCastException cce = new ClassCastException(e.getMessage());
288                        cce.setStackTrace(e.getStackTrace());
289                        throw cce;
290                        
291                }
292        }
293        
294
295        public static Array toUpperCaseArray(Key[] keys) {
296                ArrayImpl arr=new ArrayImpl();
297                for(int i=0;i<keys.length;i++) {
298                        arr._append(((KeyImpl)keys[i]).getUpperString());
299                }
300                return arr;
301        }
302        public static Array toLowerCaseArray(Key[] keys) {
303                ArrayImpl arr=new ArrayImpl();
304                for(int i=0;i<keys.length;i++) {
305                        arr._append(((KeyImpl)keys[i]).getLowerString());
306                }
307                return arr;
308        }
309        
310        public static Array toArray(Key[] keys) {
311                ArrayImpl arr=new ArrayImpl();
312                for(int i=0;i<keys.length;i++) {
313                        arr._append(((KeyImpl)keys[i]).getString());
314                }
315                return arr;
316        }
317
318        public static String toUpperCaseList(Key[] array, String delimiter) {
319                if(array.length==0) return "";
320                StringBuffer sb=new StringBuffer(((KeyImpl)array[0]).getUpperString());
321                
322                if(delimiter.length()==1) {
323                        char c=delimiter.charAt(0);
324                        for(int i=1;i<array.length;i++) {
325                                sb.append(c);
326                                sb.append(((KeyImpl)array[i]).getUpperString());
327                        }
328                }
329                else {
330                        for(int i=1;i<array.length;i++) {
331                                sb.append(delimiter);
332                                sb.append(((KeyImpl)array[i]).getUpperString());
333                        }
334                }
335                return sb.toString();
336        }
337
338        public static String toList(Key[] array, String delimiter) {
339                if(array.length==0) return "";
340                StringBuilder sb=new StringBuilder(((KeyImpl)array[0]).getString());
341                
342                if(delimiter.length()==1) {
343                        char c=delimiter.charAt(0);
344                        for(int i=1;i<array.length;i++) {
345                                sb.append(c);
346                                sb.append((array[i]).getString());
347                        }
348                }
349                else {
350                        for(int i=1;i<array.length;i++) {
351                                sb.append(delimiter);
352                                sb.append((array[i]).getString());
353                        }
354                }
355                return sb.toString();
356        }
357
358        public static String toLowerCaseList(Key[] array, String delimiter) {
359                if(array.length==0) return "";
360                StringBuffer sb=new StringBuffer(((KeyImpl)array[0]).getLowerString());
361                
362                if(delimiter.length()==1) {
363                        char c=delimiter.charAt(0);
364                        for(int i=1;i<array.length;i++) {
365                                sb.append(c);
366                                sb.append(((KeyImpl)array[i]).getLowerString());
367                        }
368                }
369                else {
370                        for(int i=1;i<array.length;i++) {
371                                sb.append(delimiter);
372                                sb.append(((KeyImpl)array[i]).getLowerString());
373                        }
374                }
375                return sb.toString();
376        }
377
378        public static Collection.Key toKey(Object obj, Collection.Key defaultValue) {
379                if(obj instanceof Collection.Key) return (Collection.Key) obj;
380                String str = Caster.toString(obj,null);
381                if(str==null) return defaultValue;
382                return init(str);
383        }
384
385        public static Collection.Key toKey(Object obj) throws CasterException {
386                if(obj instanceof Collection.Key) return (Collection.Key) obj;
387                String str = Caster.toString(obj,null);
388                if(str==null) throw new CasterException(obj,Collection.Key.class);
389                return init(str);
390        }
391
392
393        public long sizeOf() {
394                return 
395                SizeOf.size(this.key)+
396                SizeOf.size(this.lcKey)+
397                SizeOf.size(this.ucKey)+
398                SizeOf.REF_SIZE;
399        }
400
401        @Override
402        public int length() {
403                return key.length();
404        }
405
406
407        public static Key[] toKeyArray(String[] arr) {
408                if(arr==null) return null;
409                
410                Key[] keys=new Key[arr.length];
411                for(int i=0;i<keys.length;i++){
412                        keys[i]=init(arr[i]);
413                }
414                return keys;
415        }         
416}