001package com.hfg.util;
002
003
004import java.util.Collection;
005import java.util.Date;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.Map;
009import java.util.Set;
010
011import com.hfg.util.collection.CollectionUtil;
012
013//------------------------------------------------------------------------------
014/**
015 Enumeration of basic data types.
016
017 @author J. Alex Taylor, hairyfatguy.com
018 */
019//------------------------------------------------------------------------------
020// com.hfg XML/HTML Coding Library
021//
022// This library is free software; you can redistribute it and/or
023// modify it under the terms of the GNU Lesser General Public
024// License as published by the Free Software Foundation; either
025// version 2.1 of the License, or (at your option) any later version.
026//
027// This library is distributed in the hope that it will be useful,
028// but WITHOUT ANY WARRANTY; without even the implied warranty of
029// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
030// Lesser General Public License for more details.
031//
032// You should have received a copy of the GNU Lesser General Public
033// License along with this library; if not, write to the Free Software
034// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
035//
036// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
037// jataylor@hairyfatguy.com
038//------------------------------------------------------------------------------
039
040public enum DataType
041{
042   BIGINT,
043   BOOLEAN,
044   CHARACTER,
045   DATE,
046   DOUBLE,
047   FLOAT,
048   INTEGER,
049   NUMBER,
050   STRING;
051
052   private static Map<String, DataType> sLookupMap;
053   static
054   {
055      BIGINT.addAltName("long");
056      BOOLEAN.addAltName("bool");
057      CHARACTER.addAltName("char");
058      DATE.addAltName("datetime");
059      DOUBLE.addAltName("real");
060      INTEGER.addAltName("int");
061      NUMBER.addAltName("num");
062      STRING.addAltName("varchar");
063   }
064
065   private Set<String> mAltNames;
066
067
068   //---------------------------------------------------------------------------
069   /**
070    * Since an Enum's valueOf() can't be overridden, this is our more flexible lookup.
071    * @param inClass the Class value for which the corresponding DataType will be returned
072    * @return the DataType corresponding to the specified Class value
073    */
074   public static DataType fromClass(Class inClass)
075   {
076      DataType value = null;
077
078      if (inClass != null)
079      {
080         if (String.class.isAssignableFrom(inClass))
081         {
082            value = DataType.STRING;
083         }
084         else if (Double.class.isAssignableFrom(inClass))
085         {
086            value = DataType.DOUBLE;
087         }
088         else if (Float.class.isAssignableFrom(inClass))
089         {
090            value = DataType.FLOAT;
091         }
092         else if (Integer.class.isAssignableFrom(inClass))
093         {
094            value = DataType.INTEGER;
095         }
096         else if (Long.class.isAssignableFrom(inClass))
097         {
098            value = DataType.BIGINT;
099         }
100         else if (Boolean.class.isAssignableFrom(inClass))
101         {
102            value = DataType.BOOLEAN;
103         }
104         else if (Date.class.isAssignableFrom(inClass))
105         {
106            value = DataType.DATE;
107         }
108         else if (Character.class.isAssignableFrom(inClass))
109         {
110            value = DataType.CHARACTER;
111         }
112      }
113
114      return value;
115   }
116
117   //---------------------------------------------------------------------------
118   /**
119    * Since an Enum's valueOf() can't be overridden, this is our more flexible lookup.
120    * @param inStringValue the String value for which the corresponding DataType will be returned
121    * @return the DataType corresponding to the specified String value
122    */
123   public static DataType fromString(String inStringValue)
124   {
125      if (null == sLookupMap)
126      {
127         initLookupMap();
128      }
129
130      return sLookupMap.get(inStringValue != null ? inStringValue.toLowerCase() : null);
131   }
132
133   //---------------------------------------------------------------------------
134   private static synchronized void initLookupMap()
135   {
136      sLookupMap = new HashMap<>(20);
137      for (DataType value : values())
138      {
139         sLookupMap.put(value.name().toLowerCase(), value);
140         if (CollectionUtil.hasValues(value.mAltNames))
141         {
142            for (String altName : value.mAltNames)
143            {
144               sLookupMap.put(altName.toLowerCase(), value);
145            }
146         }
147      }
148   }
149
150   //---------------------------------------------------------------------------
151   private DataType addAltName(String inValue)
152   {
153      if (null == mAltNames)
154      {
155         mAltNames = new HashSet<>(2);
156      }
157
158      mAltNames.add(inValue.toLowerCase());
159
160      return this;
161   }
162
163   //---------------------------------------------------------------------------
164   public boolean isNumeric()
165   {
166      return (this == BIGINT || this == DOUBLE || this == FLOAT || this == INTEGER || this == NUMBER);   
167   }
168   
169   //---------------------------------------------------------------------------
170   public static DataType guessDataTypeFromValues(Collection<? extends Object> inDataValues)
171   {
172      DataType returnDataType = DataType.STRING;
173
174
175      if (CollectionUtil.hasValues(inDataValues))
176      {
177         // Extract the unique set of Classes represented by the specified data values
178         Set<Class> dataTypeClasses = new HashSet<>(4);
179         for (Object value : inDataValues)
180         {
181            if (value != null)
182            {
183               dataTypeClasses.add(value.getClass());
184            }
185         }
186
187         // Convert the Set of Classes to a Set of DataTypes
188         Set<DataType> dataTypes = new HashSet<>(4);
189         for (Class clazz : dataTypeClasses)
190         {
191            DataType dataType = DataType.fromClass(clazz);
192            if (dataType != null)
193            {
194               dataTypes.add(dataType);
195            }
196         }
197
198         if (1 == dataTypes.size())
199         {
200            returnDataType = dataTypes.iterator().next();
201         }
202         else
203         {
204            // There are a mixture of DataTypes.
205            // Just return STRING unless all the types are numeric.
206            boolean allNumeric = true;
207            for (DataType dataType : dataTypes)
208            {
209               if (! dataType.isNumeric())
210               {
211                  allNumeric = false;
212                  break;
213               }
214            }
215
216            if (allNumeric)
217            {
218               // All DataTypes present are numeric - return the broadest type.
219               // For example if the DataTypes present are DOUBLE and INTEGER, return DOUBLE.
220               if (dataTypes.contains(DataType.DOUBLE))
221               {
222                  returnDataType = DataType.DOUBLE;
223               }
224               else if (dataTypes.contains(DataType.FLOAT))
225               {
226                  returnDataType = DataType.FLOAT;
227               }
228               else if (dataTypes.contains(DataType.BIGINT))
229               {
230                  returnDataType = DataType.BIGINT;
231               }
232            }
233         }
234      }
235
236      return returnDataType;
237   }
238}