001/**
002 *
003 * Copyright © 2014-2024 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jxmpp.jid.util;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Set;
025
026import org.jxmpp.jid.EntityBareJid;
027import org.jxmpp.jid.DomainFullJid;
028import org.jxmpp.jid.EntityFullJid;
029import org.jxmpp.jid.Jid;
030import org.jxmpp.jid.impl.JidCreate;
031import org.jxmpp.stringprep.XmppStringprepException;
032import org.jxmpp.util.XmppStringUtils;
033
034public class JidUtil {
035
036        /**
037         * Check if the given CharSequence represents a typical and valid entity bare JID. This method does perform the same
038         * check as {@link #isValidEntityBareJid(CharSequence)} and additionally verifies that the domainpart of the JID
039         * contains at least one dot ('.') character.
040         * <p>
041         * For more information about the different verification methods see {@link #validateEntityBareJid(CharSequence)}.
042         * </p>
043         * 
044         * @param jid the CharSequence to check.
045         * @return true if {@code jid} represents a valid entity bare JID, false otherwise
046         * @see #isValidEntityBareJid(CharSequence)
047         * @see EntityBareJid
048         */
049        public static boolean isTypicalValidEntityBareJid(CharSequence jid) {
050                try {
051                        validateTypicalEntityBareJid(jid);
052                } catch (NotAEntityBareJidStringException | XmppStringprepException e) {
053                        return false;
054                }
055                return true;
056        }
057
058        /**
059         * Check if the given CharSequence is a typical and valid entity bare JID. This method does perform the same
060         * check as {@link #isValidEntityBareJid(CharSequence)} and additionally verifies that the domainpart of the JID
061         * contains at least one dot ('.') character.
062         * <p>
063         * The <code>…TypicalValidEntityBareJid(CharSequence)</code> methods are useful if you expect your users to always
064         * enter a FQDN as domainpart. Whereas <code>isValidEntityBareJid(CharSequence)</code> and
065         * <code>validateEntityBareJid</code> accept also inputs like "foo@example", the "is typical JID" methods require
066         * the domainpart to contain a dot, e.g. "foo@example.org".
067         * </p>
068         * 
069         * @param jidcs the JID CharSequence
070         * @return a BareJid instance representing the given JID CharSequence
071         * @throws NotAEntityBareJidStringException if the given CharSequence is not a bare JID.
072         * @throws XmppStringprepException if an error happens.
073         */
074        public static EntityBareJid validateTypicalEntityBareJid(CharSequence jidcs) throws NotAEntityBareJidStringException, XmppStringprepException {
075                EntityBareJid jid = validateEntityBareJid(jidcs);
076                if (jid.getDomain().toString().indexOf('.') == -1) {
077                        throw new NotAEntityBareJidStringException("Domainpart does not include a dot ('.') character");
078                }
079                return jid;
080        }
081
082        /**
083         * Check if the given CharSequence represents a valid entity bare JID. That
084         * is, it must consists exactly of a local- and a domainpart
085         * (&lt;localpart@domainpart&gt;).
086         * <p>
087         * This method is meant to validate user input and give fast feedback (e.g.
088         * with a red or green light) about if the user entered CharSequence
089         * represents a bare JID.
090         * </p>
091         * 
092         * @param jid
093         *            the CharSequence to check.
094         * @return true if {@code jid} represents a valid entity bare JID, false otherwise
095         * @see EntityBareJid
096         */
097        public static boolean isValidEntityBareJid(CharSequence jid) {
098                try {
099                        validateEntityBareJid(jid);
100                } catch (NotAEntityBareJidStringException | XmppStringprepException e) {
101                        return false;
102                }
103                return true;
104        }
105
106        /**
107         * Check if the given CharSequence is a valid entity bare JID. That
108         * is, it must consists exactly of a local- and a domainpart
109         * (&lt;localpart@domainpart&gt;).
110         * <p>
111         * This is a convenience method meant to validate user entered bare JIDs. If
112         * the given {@code jid} is not a valid bare JID, then this method will
113         * throw either {@link NotAEntityBareJidStringException} or
114         * {@link XmppStringprepException}. The NotABareJidStringException will
115         * contain a meaningful message explaining why the given CharSequence is not a
116         * valid bare JID (e.g. "does not contain a '@' character").
117         * </p>
118         * 
119         * @param jidcs the JID CharSequence
120         * @return a BareJid instance representing the given JID CharSequence
121         * @throws NotAEntityBareJidStringException if the given CharSequence is not a bare JID.
122         * @throws XmppStringprepException if an error happens.
123         */
124        public static EntityBareJid validateEntityBareJid(CharSequence jidcs) throws NotAEntityBareJidStringException, XmppStringprepException {
125                String jid = jidcs.toString();
126                final int atIndex = jid.indexOf('@');
127                if (atIndex == -1) {
128                        throw new NotAEntityBareJidStringException("'" + jid + "' does not contain a '@' character");
129                } else if (jid.indexOf('@', atIndex + 1) != -1) {
130                        throw new NotAEntityBareJidStringException("'" + jid + "' contains multiple '@' characters");
131                }
132                final String localpart = XmppStringUtils.parseLocalpart(jid);
133                if (localpart == null || localpart.length() == 0) {
134                        throw new NotAEntityBareJidStringException("'" + jid + "' has empty localpart");
135                }
136                final String domainpart = XmppStringUtils.parseDomain(jid);
137                if (domainpart == null || domainpart.length() == 0) {
138                        throw new NotAEntityBareJidStringException("'" + jid + "' has empty domainpart");
139                }
140                return JidCreate.entityBareFromUnescaped(jid);
141        }
142
143        public static class NotAEntityBareJidStringException extends Exception {
144                /**
145                 * 
146                 */
147                private static final long serialVersionUID = -1710386661031655082L;
148
149                /**
150                 * Construct a new "not a entity bare JID" exception.
151                 *
152                 * @param message the message of the exception.
153                 */
154                public NotAEntityBareJidStringException(String message) {
155                        super(message);
156                }
157        }
158
159        /**
160         * Filter all entity bare JIDs.
161         *
162         * @param in the input collection.
163         * @param out the collection where the filtered JIDs are added to.
164         */
165        public static void filterEntityBareJid(Collection<? extends Jid> in, Collection<? super EntityBareJid> out) {
166                for (Jid jid : in) {
167                        EntityBareJid bareJid = jid.asEntityBareJidIfPossible();
168                        if (bareJid != null) {
169                                out.add(bareJid);
170                        }
171                }
172        }
173
174        /**
175         * Filter all entity bare JIDs.
176         *
177         * @param input the input collection.
178         * @return a set containing all bare JIDs of the input collection.
179         */
180        public static Set<EntityBareJid> filterEntityBareJidSet(Collection<? extends Jid> input) {
181                Set<EntityBareJid> res = new HashSet<EntityBareJid>(input.size());
182                filterEntityBareJid(input, res);
183                return res;
184        }
185
186        /**
187         * Filter all entity bare JIDs.
188         *
189         * @param input the input collection.
190         * @return a list containing all bare JIDs of the input collection.
191         */
192        public static List<EntityBareJid> filterEntityBareJidList(Collection<? extends Jid> input) {
193                List<EntityBareJid> res = new ArrayList<EntityBareJid>(input.size());
194                filterEntityBareJid(input, res);
195                return res;
196        }
197
198        /**
199         * Filter all entity full JIDs.
200         *
201         * @param in the input collection.
202         * @param out the collection where the filtered JIDs are added to.
203         */
204        public static void filterEntityFullJid(Collection<? extends Jid> in, Collection<? super EntityFullJid> out) {
205                for (Jid jid : in) {
206                        EntityFullJid fullJid = jid.asEntityFullJidIfPossible();
207                        if (fullJid != null) {
208                                out.add(fullJid);
209                        }
210                }
211        }
212
213        /**
214         * Filter all full JIDs.
215         *
216         * @param input the input collection.
217         * @return a set containing all full JIDs of the input collection.
218         */
219        public static Set<EntityFullJid> filterEntityFullJidSet(Collection<? extends Jid> input) {
220                Set<EntityFullJid> res = new HashSet<EntityFullJid>(input.size());
221                filterEntityFullJid(input, res);
222                return res;
223        }
224
225        /**
226         * Filter all full JIDs.
227         *
228         * @param input the input collection.
229         * @return a list containing all full JIDs of the input collection.
230         */
231        public static List<EntityFullJid> filterEntityFullJidList(Collection<? extends Jid> input) {
232                List<EntityFullJid> res = new ArrayList<EntityFullJid>(input.size());
233                filterEntityFullJid(input, res);
234                return res;
235        }
236
237        /**
238         * Filter all domain full JIDs.
239         *
240         * @param in the input collection.
241         * @param out the collection where the filtered JIDs are added to.
242         */
243        public static void filterDomainFullJid(Collection<? extends Jid> in, Collection<? super DomainFullJid> out) {
244                for (Jid jid : in) {
245                        DomainFullJid domainFullJid = jid.asDomainFullJidIfPossible();
246                        if (domainFullJid != null) {
247                                out.add(domainFullJid);
248                        }
249                }
250        }
251
252        /**
253         * Filter all domain full JIDs.
254         *
255         * @param input the input collection.
256         * @return a set containing all domain full JIDs of the input collection.
257         */
258        public static Set<DomainFullJid> filterDomainFullJidSet(Collection<? extends Jid> input) {
259                Set<DomainFullJid> res = new HashSet<DomainFullJid>(input.size());
260                filterDomainFullJid(input, res);
261                return res;
262        }
263
264        /**
265         * Filter all domain full JIDs.
266         *
267         * @param input the input collection.
268         * @return a list containing all domain full JIDs of the input collection.
269         */
270        public static List<DomainFullJid> filterDomainFullJidList(Collection<? extends Jid> input) {
271                List<DomainFullJid> res = new ArrayList<DomainFullJid>(input.size());
272                filterDomainFullJid(input, res);
273                return res;
274        }
275
276        /**
277         * Convert the given collection of CharSequences to bare JIDs.
278         *
279         * @param jidStrings the collection of CharSequences.
280         * @return a set of bare JIDs.
281         */
282        public static Set<EntityBareJid> entityBareJidSetFrom(Collection<? extends CharSequence> jidStrings) {
283                Set<EntityBareJid> res = new HashSet<EntityBareJid>(jidStrings.size());
284                entityBareJidsFrom(jidStrings, res, null);
285                return res;
286        }
287
288        /**
289         * Convert a collection of Strings to a Set of {@link EntityBareJid}'s.
290         * <p>
291         * If the optional argument <code>exceptions</code> is given, then all {@link XmppStringprepException} thrown while
292         * converting will be added to the list. Otherwise, if an XmppStringprepExceptions is thrown, it will be wrapped in
293         * a AssertionError Exception and throw.
294         * </p>
295         * 
296         * @param jidStrings
297         *            the strings that are going to get converted
298         * @param output
299         *            the collection where the BareJid's will be added to
300         * @param exceptions the list of exceptions thrown while converting.
301         */
302        public static void entityBareJidsFrom(Collection<? extends CharSequence> jidStrings, Collection<? super EntityBareJid> output,
303                        List<XmppStringprepException> exceptions) {
304                for (CharSequence jid : jidStrings) {
305                        try {
306                                EntityBareJid bareJid = JidCreate.entityBareFrom(jid);
307                                output.add(bareJid);
308                        } catch (XmppStringprepException e) {
309                                if (exceptions != null) {
310                                        exceptions.add(e);
311                                } else {
312                                        throw new AssertionError(e);
313                                }
314                        }
315                }
316        }
317
318        /**
319         * Convert the given array of Strings to JIDs.
320         * <p>
321         * Note that errors while converting the Strings will be silently ignored.
322         * </p>
323         * 
324         * @param jids a array of JID Strings.
325         * @return a set of JIDs.
326         */
327        public static Set<Jid> jidSetFrom(String[] jids) {
328                return jidSetFrom(Arrays.asList(jids));
329        }
330
331        /**
332         * Convert the given collection of CharSequences to JIDs.
333         *
334         * @param jidStrings the collection of CharSequences.
335         * @return a set of JIDs.
336         */
337        public static Set<Jid> jidSetFrom(Collection<? extends CharSequence> jidStrings) {
338                Set<Jid> res = new HashSet<Jid>(jidStrings.size());
339                jidsFrom(jidStrings, res, null);
340                return res;
341        }
342
343        /**
344         * Convert a collection of Strings to a Set of {@link Jid}'s.
345         * <p>
346         * If the optional argument <code>exceptions</code> is given, then all {@link XmppStringprepException} thrown while
347         * converting will be added to the list. Otherwise, if an XmppStringprepExceptions is thrown, it will be wrapped in
348         * a AssertionError Exception and throw.
349         * </p>
350         * 
351         * @param jidStrings
352         *            the strings that are going to get converted
353         * @param output
354         *            the collection where the Jid's will be added to
355         * @param exceptions the list of exceptions thrown while converting.
356         */
357        public static void jidsFrom(Collection<? extends CharSequence> jidStrings, Collection<? super Jid> output,
358                        List<XmppStringprepException> exceptions) {
359                for (CharSequence jidString : jidStrings) {
360                        try {
361                                Jid jid = JidCreate.from(jidString);
362                                output.add(jid);
363                        } catch (XmppStringprepException e) {
364                                if (exceptions != null) {
365                                        exceptions.add(e);
366                                } else {
367                                        throw new AssertionError(e);
368                                }
369                        }
370                }
371        }
372
373        /**
374         * Convert a collection of JIDs to a list of Strings representing those JIDs.
375         *
376         * @param jids a collection of JIDs.
377         * @return a list of Strings.
378         */
379        public static List<String> toStringList(Collection<? extends Jid> jids) {
380                List<String> res = new ArrayList<String>(jids.size());
381                toStrings(jids, res);
382                return res;
383        }
384
385        /**
386         * convert a collection of JIDs to a set of Strings representing those JIDs.
387         *
388         * @param jids a collection of JIDs.
389         * @return a set of String.
390         */
391        public static Set<String> toStringSet(Collection<? extends Jid> jids) {
392                Set<String> res = new HashSet<String>(jids.size());
393                toStrings(jids, res);
394                return res;
395        }
396
397        /**
398         * Convert a collection of JIDs to a Collection of Strings.
399         *
400         * @param jids the collection of Strings to convert.
401         * @param jidStrings the collection of Strings to append to.
402         */
403        public static void toStrings(Collection<? extends Jid> jids, Collection<? super String> jidStrings) {
404                for (Jid jid : jids) {
405                        jidStrings.add(jid.toString());
406                }
407        }
408
409        /**
410         * Check if two JIDs are equals. Takes <code>null</code> values into consideration. Which means that this method will return <code>true</code> if both JIDs are <code>null</code>.
411         *
412         * @param jidOne The first JID to compare.
413         * @param jidTwo The second JID to compare.
414         * @return <code>true</code> if both JIDs are equals.
415         * @since 0.7.0
416         */
417        public static boolean equals(Jid jidOne, Jid jidTwo) {
418                if (jidOne != null) {
419                        return jidOne.equals(jidTwo);
420                }
421
422                return jidTwo == null;
423        }
424}