001/*
002 * lang-tag
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.langtag;
019
020
021import java.util.*;
022
023
024/**
025 * Language tag utilities.
026 */
027public final class LangTagUtils {
028
029
030        /**
031         * Strips the language tag, if any is found, from the specified string.
032         * This method is {@code null} safe.
033         *
034         * <p>Example:
035         *
036         * <pre>
037         * "name#bg-BG" => "name"
038         * "name"       => "name"
039         * </pre>
040         *
041         * @param s The string. May contain a language tag. May be
042         *          {@code null}.
043         *
044         * @return The string with no language tag, or {@code null} if the
045         *         original string is {@code null}.
046         */
047        public static String strip(final String s) {
048
049                if (s == null)
050                        return null;
051
052                final int pos = s.indexOf('#');
053
054                if (pos < 0)
055                        return s;
056
057                return s.substring(0, pos);
058        }
059
060
061        /**
062         * Strips the language tags, if any are found, from the specified
063         * string set. This method is {@code null} safe.
064         *
065         * <p>Example:
066         *
067         * <pre>
068         * "name#bg-BG" => "name"
069         * "name"       => "name"
070         * </pre>
071         *
072         * @param set The string set. May contain strings with language tags.
073         *            May be {@code null}.
074         *
075         * @return The string set with no language tags, or {@code null} if the
076         *         original set is {@code null}.
077         */
078        public static Set<String> strip(final Set<String> set) {
079
080                if (set == null)
081                        return null;
082
083                Set<String> out = new HashSet<String>();
084
085                for (String s: set)
086                        out.add(strip(s));
087
088                return out;
089        }
090
091
092        /**
093         * Strips the language tags, if any are found, from the specified
094         * string list. This method is {@code null} safe.
095         *
096         * <p>Example:
097         *
098         * <pre>
099         * "name#bg-BG" => "name"
100         * "name"       => "name"
101         * </pre>
102         *
103         * @param list The string list. May contain strings with language tags.
104         *             May be {@code null}.
105         *
106         * @return The string list with no language tags, or {@code null} if
107         *         the original list is {@code null}.
108         */
109        public static List<String> strip(final List<String> list) {
110
111                if (list == null)
112                        return null;
113
114                List<String> out = new ArrayList<String>(list.size());
115
116                for (String s: list)
117                        out.add(strip(s));
118
119                return out;
120        }
121
122
123        /**
124         * Extracts the language tag, if any is found, from the specified
125         * string.
126         *
127         * <p>Example:
128         *
129         * <pre>
130         * "name#bg-BG" => "bg-BG"
131         * "name#"      => null
132         * "name"       => null
133         * </pre>
134         *
135         * @param s The string. May contain a language tag. May be
136         *          {@code null}.
137         *
138         * @return The extracted language tag, {@code null} if not found.
139         *
140         * @throws LangTagException If the language tag is invalid.
141         */
142        public static LangTag extract(final String s)
143                throws LangTagException {
144
145                if (s == null)
146                        return null;
147
148                final int pos = s.indexOf('#');
149
150                if (pos < 0 || s.length() < pos + 1)
151                        return null;
152
153                return LangTag.parse(s.substring(pos + 1));
154        }
155
156
157        /**
158         * Finds all language-tagged entries with the specified base name.
159         * Entries with invalid language tags will be skipped.
160         *
161         * <p>Example:
162         *
163         * <p>Map to search for base name "month":
164         *
165         * <pre>
166         * "month"    => "January"
167         * "month#de" => "Januar"
168         * "month#fr" => "janvier"
169         * "month#pt" => "janeiro"
170         * </pre>
171         *
172         * <p>Result:
173         *
174         * <pre>
175         * null => "January"
176         * "de" => "Januar"
177         * "fr" => "janvier"
178         * "pt" => "janeiro"
179         * </pre>
180         *
181         * @param baseName The base name to look for (without a language tag)
182         *                 in the map keys. Must not be {@code null}.
183         * @param map      The map to search. Must not be {@code null}.
184         *
185         * @return A map of all language-tagged entries with the specified
186         *         base name. A {@code null} keyed entry will indicate no
187         *         language tag (base name only).
188         */
189        public static <T> Map<LangTag,T> find(final String baseName, final Map<String,T> map) {
190
191                Map<LangTag,T> result = new HashMap<LangTag,T>();
192
193                // Walk through each map entry, checking for entry keys that
194                // start with "baseName"
195                for (Map.Entry<String,T> entry: map.entrySet()) {
196
197                        T value;
198
199                        try {
200                                value = entry.getValue();
201
202                        } catch (ClassCastException e) {
203
204                                continue; // skip
205                        }
206                        
207                        if (entry.getKey().equals(baseName)) {
208
209                                // Claim name matches, no tag   
210                                result.put(null, value);
211                        }
212                        else if (entry.getKey().startsWith(baseName + '#')) {
213
214                                // Claim name matches, has tag
215                                String[] parts = entry.getKey().split("#", 2);
216
217                                LangTag langTag = null;
218
219                                if (parts.length == 2) {
220                                        
221                                        try {
222                                                langTag = LangTag.parse(parts[1]);
223                                                
224                                        } catch (LangTagException e) {
225
226                                                // ignore
227                                        }
228                                }
229
230                                result.put(langTag, value);
231                        }
232                }
233
234                return result;
235        }
236
237
238        /**
239         * Returns a string list representation of the specified language tags
240         * collection.
241         *
242         * @param langTags The language tags list. May be {@code null}.
243         *
244         * @return The string list, or {@code null} if the original list is
245         *         {@code null}.
246         */
247        public static List<String> toStringList(final Collection<LangTag> langTags) {
248
249                if (langTags == null)
250                        return null;
251
252                List<String> out = new ArrayList<String>(langTags.size());
253
254                for (LangTag lt: langTags) {
255                        out.add(lt.toString());
256                }
257
258                return out;
259        }
260
261
262        /**
263         * Returns a string array representation of the specified language tags
264         * collection.
265         *
266         * @param langTags The language tags list. May be {@code null}.
267         *
268         * @return The string list, or {@code null} if the original list is
269         *         {@code null}.
270         */
271        public static String[] toStringArray(final Collection<LangTag> langTags) {
272
273                if (langTags == null)
274                        return null;
275
276                String[] out = new String[langTags.size()];
277
278                int i=0;
279
280                for (LangTag lt: langTags) {
281                        out[i++] = lt.toString();
282                }
283
284                return out;
285        }
286        
287        
288        /**
289         * Parses a language tag list from the specified string containing
290         * space delimited values.
291         *
292         * @param spaceDelimitedValues String containing zero or more space
293         *                             delimited values. May be {@code null}.
294         *
295         * @return The language tag list, or {@code null} if the parsed string
296         *         is {@code null}.
297         *
298         * @throws LangTagException If parsing failed.
299         */
300        public static List<LangTag> parseLangTagList(final String spaceDelimitedValues)
301                throws LangTagException {
302                
303                if (spaceDelimitedValues == null) {
304                        return null;
305                }
306                
307                List<LangTag> out = new LinkedList<LangTag>();
308                
309                StringTokenizer st = new StringTokenizer(spaceDelimitedValues, " ");
310                
311                while (st.hasMoreTokens()) {
312                        out.add(LangTag.parse(st.nextToken()));
313                }
314                
315                return out;
316        }
317
318
319        /**
320         * Parses a language tag list from the specified string collection.
321         *
322         * @param collection The string collection. May be {@code null}.
323         *
324         * @return The language tag list, or {@code null} if the parsed string
325         *         collection is {@code null}.
326         *
327         * @throws LangTagException If parsing failed.
328         */
329        public static List<LangTag> parseLangTagList(final Collection<String> collection)
330                throws LangTagException {
331
332                if (collection == null)
333                        return null;
334
335                List<LangTag> out = new ArrayList<LangTag>(collection.size());
336
337                for (String s: collection) {
338                        out.add(LangTag.parse(s));
339                }
340
341                return out;
342        }
343
344
345        /**
346         * Parses a language tag list from the specified string values.
347         *
348         * @param values The string values. May be {@code null}.
349         *
350         * @return The language tag list, or {@code null} if the parsed string
351         *         array is {@code null}.
352         *
353         * @throws LangTagException If parsing failed.
354         */
355        public static List<LangTag> parseLangTagList(final String ... values)
356                throws LangTagException {
357
358                if (values == null)
359                        return null;
360
361                List<LangTag> out = new ArrayList<LangTag>(values.length);
362
363                for (String s: values) {
364                        out.add(LangTag.parse(s));
365                }
366
367                return out;
368        }
369
370
371        /**
372         * Parses a language tag array from the specified string values.
373         *
374         * @param values The string values. May be {@code null}.
375         *
376         * @return The language tag array, or {@code null} if the parsed string
377         *         array is {@code null}.
378         *
379         * @throws LangTagException If parsing failed.
380         */
381        public static LangTag[] parseLangTagArray(final String ... values)
382                throws LangTagException {
383
384                if (values == null)
385                        return null;
386
387                LangTag[] out = new LangTag[values.length];
388
389                for (int i=0; i < values.length; i++) {
390                        out[i] = LangTag.parse(values[i]);
391                }
392
393                return out;
394        }
395        
396        
397        /**
398         * Splits the specified optionally language tagged string into a string
399         * and language tag pair.
400         *
401         * @param s The optionally language-tagged string. May be {@code null}.
402         *
403         * @return The pair, with {@code null} language tag if none found.
404         *         {@code null} if the original value is {@code null}.
405         *
406         * @throws LangTagException If parsing failed.
407         */
408        public static Map.Entry<String,LangTag> split(final String s)
409                throws LangTagException {
410                
411                if (s == null)
412                        return null;
413                
414                if ("#".equals(s))
415                        return new AbstractMap.SimpleImmutableEntry<String, LangTag>("#", null);
416                
417                final int pos = s.indexOf('#');
418                
419                if (pos < 0 || s.length() < pos + 1)
420                        return new AbstractMap.SimpleImmutableEntry<String, LangTag>(s, null);
421                
422                return new AbstractMap.SimpleImmutableEntry<String, LangTag>(
423                        s.substring(0, pos),
424                        LangTag.parse(s.substring(pos + 1)));
425        }
426        
427        
428        /**
429         * Concatenates the specified language tags with a single space.
430         *
431         * @param langTags The language tags list. May be {@code null}.
432         *
433         * @return The string, {@code null} if the language tag list was
434         *         {@code null} or empty.
435         */
436        public static String concat(final List<LangTag> langTags) {
437                
438                if (langTags == null || langTags.isEmpty()) {
439                        return null;
440                }
441                
442                StringBuilder sb = new StringBuilder();
443                
444                for (LangTag tag: langTags) {
445                        
446                        if (tag == null) {
447                                continue;
448                        }
449                        
450                        if (sb.length() > 0)
451                                sb.append(' ');
452                        
453                        sb.append(tag);
454                }
455                
456                return sb.toString();
457        }
458
459
460        /**
461         * Prevents public instantiation.
462         */
463        private LangTagUtils() { }
464}