001/**
002 * Copyright (C) 2014  Universidade de Aveiro, DETI/IEETA, Bioinformatics Group - http://bioinformatics.ua.pt/
003 *
004 * This file is part of Dicoogle/dicoogle-sdk.
005 *
006 * Dicoogle/dicoogle-sdk is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * Dicoogle/dicoogle-sdk 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
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with Dicoogle.  If not, see <http://www.gnu.org/licenses/>.
018 */
019package pt.ua.dicoogle.sdk.settings;
020
021import java.util.Map;
022import java.util.HashMap;
023import org.apache.commons.lang3.StringEscapeUtils;
024import pt.ua.dicoogle.sdk.settings.types.GenericSetting;
025
026/**
027 * Helper for parsing form input values and creating HTML content based on them,
028 * server side.
029 *
030 * @author António Novo <antonio.novo@ua.pt>
031 */
032public class Utils
033{
034        /**
035         * This converts and applies the new textual values into the originalSettings HashMap, taking into account their original class/type.
036         * Only the settings that exist on both Maps are updated, this avoid invalid settings and Class type injections from happening.
037         *
038         * @param originalSettings the original advanced/internal settings of a plugin or service.
039         * @param newSettings the new settings to apply.
040         * @return the originalSettings HashMap with the new values in place (you can use the reference passed as originalSettings instead if you want, they will allways be the same).
041         */
042        public static HashMap<String, Object> processAdvancedSettings(HashMap<String, Object> originalSettings, HashMap<String, String[]> newSettings)
043        {
044                for (Map.Entry<String, Object> setting : originalSettings.entrySet())
045                {
046                        String name = getHTMLElementIDFromString(setting.getKey()); // NOTE remmember that the setting name is "kinda encoded" (and not in the URLEncode way, that's handled automagically)
047                        Object value = setting.getValue();
048
049                        if (newSettings.containsKey(name)) // if the setting is found on the request try to update its value (maintaining the same type ofc)
050                        {
051                                String[] newValue = newSettings.get(name);
052
053                                if (value != null)
054                                {
055                                        // parse the value depending on its class
056                                        if (value.getClass().equals(Integer.class))
057                                                value = Integer.valueOf(newValue[0]);
058                                        else if (value.getClass().equals(Float.class))
059                                                value = Float.valueOf(newValue[0]);
060                                        else if (value.getClass().equals(Boolean.class))
061                                                value = Boolean.valueOf(parseCheckBoxValue(newValue[0]));
062                                        else if (value instanceof GenericSetting)
063                                                value = ((GenericSetting) value).fromHTTPParams(newSettings, 0, name);
064                                        else // String or unrecognized class
065                                                value = newValue[0];
066
067                                        setting.setValue(value);
068                                }
069                                else // null is treated as String
070                                {
071                                        value = newValue[0];
072
073                                        setting.setValue(value);
074                                }
075
076                        }
077                        else // if the setting was not found check if it's a Boolean one, this is a FIX because browsers omit unchecked checkboxes on form action
078                        {
079                                if (value.getClass().equals(Boolean.class))
080                                        setting.setValue(new Boolean(false));
081                                else if (value instanceof GenericSetting)
082                                {
083                                        value = ((GenericSetting) value).fromHTTPParams(newSettings, 0, name);
084                                        setting.setValue(value);
085                                }
086                        }
087                }
088
089                return originalSettings; // just for easier use
090        }
091
092        /**
093         * Given the string value of a checkbox obtained from an http request,
094         * returns its boolean value.
095         *
096         * @param value the checkbox value obtained from an http request.
097         * @return the checkbox state in boolean form.
098         */
099        public static boolean parseCheckBoxValue(String value)
100        {
101                // default state is not checked
102                boolean result = false;
103
104                // if the value is defined
105                if ((value != null) && (! value.isEmpty()))
106                        result = value.equalsIgnoreCase("On"); // check if it matches the "on" string
107
108                return result;
109        }
110
111        /**
112         * Given the string value of a checkbox obtained from an http request,
113         * returns its boolean value.
114         *
115         * @param values an array of Strings where the checkbox value obtained from an http request is.
116         * @param index the index on the array where the wanted checkbox value is.
117         * @return the checkbox state in boolean form.
118         */
119        public static boolean parseCheckBoxValue(String[] values, int index)
120        {
121                // default state is not checked
122                boolean result = false;
123
124                // if the value is defined
125                if ((values != null) && (values.length > index))
126                        result = parseCheckBoxValue(values[index]);
127
128                return result;
129        }
130
131        /**
132         * Based on a input String, returns a valid HTML element name or ID for it.
133         * <b>NOTE:</b> be aware that repeated calls with the same input String
134         * will return the same result, so, on these cases name/ID collisions will happen!
135         *
136         * @param input the input String.
137         * @return a valid HTML element ID or name.
138         */
139        public static String getHTMLElementIDFromString(String input)
140        {
141                if ((input == null) || input.trim().isEmpty())
142                        return input;
143
144                input = input.trim();
145
146                // the name must start with a letter, if it doesn't them add an "s" to it
147                if (! input.substring(0, 1).matches("[A-Za-z]"))
148                        input = "s" + input;
149
150                // these are the whitelisted (valid) chars, replace all the others with nothing
151                return input.replaceAll("[^A-Za-z0-9-_]", "");
152        }
153
154        /**
155         * Based on the "real" type/class of the plugin setting Object returns the appropriate form input for it.
156         *
157         * @param name the Form Name of this HTML input.
158         * @param value a Object.
159         * @param isArrayElement if this value Object is part of a another multiple value Object.
160         * @return the appropriate form input for the supplied Object.
161         */
162        public static String getHTMLInputFromType(String name, Object value, boolean isArrayElement)
163        {
164                String result = "<input name=\"" + name + (isArrayElement ? "[]" : "") + "\" ";
165
166                if (value == null)
167                {
168                        result += "type=\"text\" value=\"\" />";
169                }
170                else if (value.getClass().equals(Integer.class))
171                {
172                        result += "type=\"number\" value=\"" + ((Integer) value).intValue() + "\" />";
173                }
174                else if (value.getClass().equals(Float.class))
175                {
176                        result += "type=\"number\" value=\"" + ((Float) value).floatValue() + "\" />";
177                }
178                else if (value.getClass().equals(Boolean.class))
179                {
180                        result += "type=\"checkbox\" " + (((Boolean) value).booleanValue() ? "checked=\"checked\"" : "") + " />";
181                }
182                else if (value instanceof GenericSetting)
183                {
184                        result = ((GenericSetting) value).toHTMLString(name + (isArrayElement ? "[]" : ""));
185                }
186                else
187                // NOTE add extra data type classes here, if needed
188                if (value.getClass().equals(String.class))
189                {
190                        result += "type=\"text\" value=\"" + StringEscapeUtils.escapeHtml4((String) value) + "\" />";
191                }
192                else // unrecognized type/class
193                {
194                        //throw new ClassCastException("Unsupported class \"" + value.getClass().getName() + "\"");
195                        //result += "type=\"text\" value=\"" + StringEscapeUtils.escapeHtml4( value.toString()) + "\" />";
196                        result += StringEscapeUtils.escapeHtml4( value.toString());                     
197                }
198
199                return result;
200        }
201
202        /**
203         * Based on the "real" type/class of the plugin setting Object returns the appropriate form input for it.
204         * This procedure espects the value Object to not be a part of a another multiple value Object.
205         *
206         * @param id the ID and Name of this HTML input.
207         * @param value a Object.
208         * @return the appropriate form input for the supplied Object.
209         */
210        public static String getHTMLInputFromType(String id, Object value)
211        {
212                return getHTMLInputFromType(id, value, false);
213        }
214
215        /**
216         * Converts a String value into the type/class of originalValue.
217         *
218         * @param originalValue the original/target type/class of the value.
219         * @param newValue the new value in String form.
220         * @return a new Object of the same class as orignalValue but it's value updated to newValue.
221         */
222        public static Object convertStringValueIntoProperType(Object originalValue, HashMap<String, String[]> params, int index, String htmlElementID)
223        {
224                Object result = null;
225
226                if (originalValue == null)
227                {
228                        result = null;
229                }
230                else if (originalValue.getClass().equals(Integer.class))
231                {
232                        String[] values = params.get(htmlElementID);
233                        if ((values == null) || (values.length <= index) || (params.get(htmlElementID)[index] == null) || (params.get(htmlElementID)[index].isEmpty()))
234                                result = 0;
235                        else
236                                result = Integer.valueOf(params.get(htmlElementID)[index]);
237                }
238                else if (originalValue.getClass().equals(Float.class))
239                {
240                        String[] values = params.get(htmlElementID);
241                        if ((values == null) || (values.length <= index) || (params.get(htmlElementID)[index] == null) || (params.get(htmlElementID)[index].isEmpty()))
242                                result = 0.0F;
243                        else
244                                result = Float.valueOf(params.get(htmlElementID)[index]);
245                }
246                else if (originalValue.getClass().equals(Boolean.class))
247                {
248                        String[] values = params.get(htmlElementID);
249                        if ((values == null) || (values.length <= index) || (params.get(htmlElementID)[index] == null) || (params.get(htmlElementID)[index].isEmpty()))
250                                result = false;
251                        else
252                                result = Boolean.valueOf(params.get(htmlElementID)[index]);
253                }
254                else if (originalValue instanceof GenericSetting)
255                {
256                        result = ((GenericSetting) originalValue).fromHTTPParams(params, index, htmlElementID);
257                }
258                else
259                // NOTE add extra data type classes here, if needed
260                if (originalValue.getClass().equals(String.class))
261                {
262                        result = params.get(htmlElementID)[index];
263                }
264                else // unrecognized type/class
265                {
266                        //throw new ClassCastException("Unsupported class \"" + value.getClass().getName() + "\"");
267                        result = originalValue;
268                }
269
270                return result;
271        }
272}