001/*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020package org.crsh.util;
021
022import javax.naming.Context;
023import java.io.ByteArrayOutputStream;
024import java.io.Closeable;
025import java.io.Flushable;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.OutputStream;
029import java.lang.reflect.ParameterizedType;
030import java.lang.reflect.Type;
031import java.lang.reflect.TypeVariable;
032import java.net.Socket;
033import java.nio.charset.Charset;
034import java.sql.*;
035import java.io.File;
036import java.net.URISyntaxException;
037import java.net.URL;
038import java.util.ArrayList;
039import java.util.Arrays;
040import java.util.Collections;
041import java.util.HashMap;
042import java.util.HashSet;
043import java.util.Iterator;
044import java.util.List;
045import java.util.Map;
046import java.util.NoSuchElementException;
047import java.util.regex.Matcher;
048import java.util.regex.Pattern;
049
050public class Utils {
051
052  /** . */
053  private static final Iterator EMPTY_ITERATOR = Collections.emptyList().iterator();
054
055  /** . */
056  private static final Pattern p = Pattern.compile("\\S+");
057
058  public static <E> Iterator<E> iterator() {
059    @SuppressWarnings("unchecked")
060    Iterator<E> iterator = (Iterator<E>)EMPTY_ITERATOR;
061    return iterator;
062  }
063
064  public static <E> Iterator<E> iterator(final E element) {
065    return new BaseIterator<E>() {
066      boolean hasNext = true;
067      @Override
068      public boolean hasNext() {
069        return hasNext;
070      }
071      @Override
072      public E next() {
073        if (hasNext) {
074          hasNext = false;
075          return element;
076        } else {
077          throw new NoSuchElementException();
078        }
079      }
080    };
081  }
082
083  public static <E> E first(Iterable<E> elements) {
084    Iterator<E> i = elements.iterator();
085    return i.hasNext() ? i.next() : null;
086  }
087
088  public static <K, V, M extends Map<K, V>> M map(M map, K key, V value) {
089    map.put(key, value);
090    return map;
091  }
092
093  public static <K, V> HashMap<K, V> map(K key, V value) {
094    HashMap<K, V> map = new HashMap<K, V>();
095    map.put(key, value);
096    return map;
097  }
098
099  public static <E> HashSet<E> set(E... elements) {
100    HashSet<E> set = new HashSet<E>(elements.length);
101    Collections.addAll(set, elements);
102    return set;
103  }
104
105  public static <E> List<E> list(E... elements) {
106    return Arrays.asList(elements);
107  }
108
109  public static <E> List<E> list(Iterable<E> iterable) {
110    return list(iterable.iterator());
111  }
112
113  public static <E> List<E> list(Iterator<E> iterator) {
114    ArrayList<E> list = new ArrayList<E>();
115    while (iterator.hasNext()) {
116      list.add(iterator.next());
117    }
118    return list;
119  }
120
121  public static int indexOf(CharSequence s, int off, char c) {
122    for (int len = s.length();off < len;off++) {
123      if (s.charAt(off) == c) {
124        return off;
125      }
126    }
127    return -1;
128  }
129
130  public static String trimLeft(String s) {
131    if (s == null) {
132      throw new NullPointerException("No null string accepted");
133    }
134    int index = 0;
135    int len = s.length();
136    while (index < len) {
137      if (s.charAt(index) == ' ') {
138        index++;
139      } else {
140        break;
141      }
142    }
143    if (index > 0) {
144      return s.substring(index);
145    } else {
146      return s;
147    }
148  }
149
150  private static final Pattern blankPattern = Pattern.compile("^\\s*$");
151
152  public static boolean notBlank(String s) {
153    return !blankPattern.matcher(s).find();
154  }
155
156  public static <E> E notNull(E e1, E e2) {
157    if (e1 != null) {
158      return e1;
159    } else {
160      return e2;
161    }
162  }
163
164  /**
165   * Return the value when it is positive otherwise return 0.
166   *
167   * @param value the value
168   * @return the non negative value
169   */
170  public static int notNegative(int value) {
171    return value >= 0 ? value : 0;
172  }
173
174  private static final Pattern FOO = Pattern.compile("" +
175      "(\\*)" + // Wildcard *
176      "|" +
177      "(\\?)" + // Wildcard ?
178      "|" +
179      "(?:\\[([^)]+)\\])" + // Range
180      "|" +
181      "(\\\\.)" // Escape
182  );
183
184  /**
185   * Create a pattern that transforms a glob expression into a regular expression, the following task are supported
186   * <ul>
187   *   <li>* : Match any number of unknown characters</li>
188   *   <li>? : Match one unknown character</li>
189   *   <li>[characters] : Match a character as part of a group of characters</li>
190   *   <li>\ : Escape character</li>
191   * </ul>
192   *
193   * @param globex the glob expression
194   * @return the regular expression
195   * @throws NullPointerException when the globex argument is null
196   */
197  public static String globexToRegex(String globex) throws NullPointerException {
198    if (globex == null) {
199      throw new NullPointerException("No null globex accepted");
200    }
201    StringBuilder regex = new StringBuilder();
202    int prev = 0;
203    Matcher matcher = FOO.matcher(globex);
204    while (matcher.find()) {
205      int next = matcher.start();
206      if (next > prev) {
207        regex.append(Pattern.quote(globex.substring(prev, next)));
208      }
209      if (matcher.group(1) != null) {
210        regex.append(".*");
211      } else if (matcher.group(2) != null) {
212        regex.append(".");
213      } else if (matcher.group(3) != null) {
214        regex.append("[");
215        regex.append(Pattern.quote(matcher.group(3)));
216        regex.append("]");
217      } else if (matcher.group(4) != null) {
218        regex.append(Pattern.quote(Character.toString(matcher.group(4).charAt(1))));
219      } else {
220        throw new UnsupportedOperationException("Not handled yet");
221      }
222      prev = matcher.end();
223    }
224    if (prev < globex.length()) {
225      regex.append(Pattern.quote(globex.substring(prev)));
226    }
227    return regex.toString();
228  }
229
230  /**
231<<<<<<< HEAD
232   * Close the socket and catch any exception thrown.
233   *
234   * @param socket the socket to close
235   * @return any Exception thrown during the <code>close</code> operation
236   */
237  public static Exception close(Socket socket) {
238    if (socket != null) {
239      try {
240        socket.close();
241      }
242      catch (Exception e) {
243        return e;
244      }
245    }
246    return null;
247  }
248
249  /**
250   * Close the closeable and catch any exception thrown.
251   *
252   * @param closeable the closeable to close
253   * @return any Exception thrown during the <code>close</code> operation
254   */
255  public static Exception close(Closeable closeable) {
256    if (closeable != null) {
257      try {
258        closeable.close();
259      }
260      catch (Exception e) {
261        return e;
262      }
263    }
264    return null;
265  }
266
267  /**
268   * Close the connection and catch any exception thrown.
269   *
270   * @param connection the socket to close
271   * @return any Exception thrown during the <code>close</code> operation
272   */
273  public static Exception close(Connection connection) {
274    if (connection != null) {
275      try {
276        connection.close();
277      }
278      catch (Exception e) {
279        return e;
280      }
281    }
282    return null;
283  }
284
285  /**
286   * Close the statement and catch any exception thrown.
287   *
288   * @param statement the statement to close
289   * @return any Exception thrown during the <code>close</code> operation
290   */
291  public static Exception close(java.sql.Statement statement) {
292    if (statement != null) {
293      try {
294        statement.close();
295      }
296      catch (Exception e) {
297        return e;
298      }
299    }
300    return null;
301  }
302
303  /**
304   * Close the result set and catch any exception thrown.
305   *
306   * @param rs the result set to close
307   * @return any Exception thrown during the <code>close</code> operation
308   */
309  public static Exception close(ResultSet rs) {
310    if (rs != null) {
311      try {
312        rs.close();
313      }
314      catch (Exception e) {
315        return e;
316      }
317    }
318    return null;
319  }
320
321  /**
322   * Close the context and catch any exception thrown.
323   *
324   * @param context the context to close
325   * @return any Exception thrown during the <code>close</code> operation
326   */
327   public static Exception close(Context context) {
328      if (context != null) {
329         try {
330            context.close();
331         }
332         catch (Exception e) {
333           return e;
334         }
335      }
336     return null;
337   }
338
339  public static <T extends Throwable> void rethrow(Class<T> throwableClass, Throwable cause) throws T {
340    T throwable;
341
342    //
343    try {
344      throwable = throwableClass.newInstance();
345    }
346    catch (Exception e) {
347      throw new AssertionError(e);
348    }
349
350    //
351    throwable.initCause(cause);
352
353    //
354    throw throwable;
355  }
356
357  public static boolean equals(Object o1, Object o2) {
358    return o1 == null ? o2 == null : (o2 != null && o1.equals(o2));
359  }
360
361  public static boolean notEquals(Object o1, Object o2) {
362    return !equals(o1, o2);
363  }
364
365  /**
366   * Flush the flushable and catch any exception thrown.
367   *
368   * @param flushable the flushable to flush
369   * @return any Exception thrown during the <code>flush</code> operation
370   */
371  public static Exception flush(Flushable flushable) {
372    if (flushable != null) {
373      try {
374        flushable.flush();
375      }
376      catch (Exception e) {
377        return e;
378      }
379    }
380    return null;
381  }
382
383  public static List<String> chunks(CharSequence s) {
384    List<String> chunks = new ArrayList<String>();
385    Matcher m = p.matcher(s);
386    while (m.find()) {
387      chunks.add(m.group());
388    }
389    return chunks;
390  }
391
392  public static String join(Iterable<String> strings, String separator) {
393    Iterator<String> i = strings.iterator();
394    if (i.hasNext()) {
395      String first = i.next();
396      if (i.hasNext()) {
397        StringBuilder buf = new StringBuilder();
398        buf.append(first);
399        while (i.hasNext()) {
400          buf.append(separator);
401          buf.append(i.next());
402        }
403        return buf.toString();
404      } else {
405        return first;
406      }
407    } else {
408      return "";
409    }
410  }
411
412  public static String[] split(CharSequence s, char separator) {
413    return foo(s, separator, 0, 0, 0);
414  }
415
416  public static String[] split(CharSequence s, char separator, int rightPadding) {
417    if (rightPadding < 0) {
418      throw new IllegalArgumentException("Right padding cannot be negative");
419    }
420    return foo(s, separator, 0, 0, rightPadding);
421  }
422
423  private static String[] foo(CharSequence s, char separator, int count, int from, int rightPadding) {
424    int len = s.length();
425    if (from < len) {
426      int to = from;
427      while (to < len && s.charAt(to) != separator) {
428        to++;
429      }
430      String[] ret;
431      if (to == len - 1) {
432        ret = new String[count + 2 + rightPadding];
433        ret[count + 1] = "";
434      }
435      else {
436        ret = to == len ? new String[count + 1 + rightPadding] : foo(s, separator, count + 1, to + 1, rightPadding);
437      }
438      ret[count] = from == to ? "" : s.subSequence(from, to).toString();
439      return ret;
440    }
441    else if (from == len) {
442      return new String[count + rightPadding];
443    }
444    else {
445      throw new AssertionError();
446    }
447  }
448
449  /**
450   * @see #findLongestCommonPrefix(Iterable)
451   */
452  public static String findLongestCommonPrefix(CharSequence... seqs) {
453    return findLongestCommonPrefix(Arrays.asList(seqs));
454  }
455
456  /**
457   * Find the longest possible common prefix of the provided char sequence.
458   *
459   * @param seqs the sequences
460   * @return the longest possible prefix
461   */
462  public static String findLongestCommonPrefix(Iterable<? extends CharSequence> seqs) {
463    String common = "";
464    out:
465    while (true) {
466      String candidate = null;
467      for (CharSequence s : seqs) {
468        if (common.length() + 1 > s.length()) {
469          break out;
470        } else {
471          if (candidate == null) {
472            candidate = s.subSequence(0, common.length() + 1).toString();
473          } else if (s.subSequence(0, common.length() + 1).toString().equals(candidate)) {
474            // Ok it is a prefix
475          } else {
476            break out;
477          }
478        }
479      }
480      if (candidate == null) {
481        break;
482      } else {
483        common = candidate;
484      }
485    }
486    return common;
487  }
488
489  public static byte[] readAsBytes(InputStream in) throws IOException {
490    return read(in).toByteArray();
491  }
492
493  public static String readAsUTF8(InputStream in) {
494    try {
495      ByteArrayOutputStream baos = read(in);
496      return baos.toString("UTF-8");
497    }
498    catch (IOException e) {
499      e.printStackTrace();
500      return null;
501    }
502  }
503
504  public static void copy(InputStream in, OutputStream out) throws IOException {
505    if (in == null) {
506      throw new NullPointerException();
507    }
508    try {
509      byte[] buffer = new byte[256];
510      for (int l = in.read(buffer); l != -1; l = in.read(buffer)) {
511        out.write(buffer, 0, l);
512      }
513    }
514    finally {
515      close(in);
516    }
517  }
518
519  private static ByteArrayOutputStream read(InputStream in) throws IOException {
520    if (in == null) {
521      throw new NullPointerException();
522    }
523    ByteArrayOutputStream baos = new ByteArrayOutputStream();
524    copy(in, baos);
525    return baos;
526  }
527
528  /*
529   * Convert an file URL to a file, avoids issues on windows with whitespaces.
530   *
531   * @param url the URL to convert
532   * @return the related file
533   * @throws java.lang.IllegalArgumentException if the url protocol is not file
534   * @throws java.lang.NullPointerException if the url argument is null
535   */
536  public static File toFile(URL url) throws IllegalArgumentException, NullPointerException {
537    if (url == null) {
538      throw new NullPointerException("No null URL accepted");
539    }
540    if (!url.getProtocol().equals("file")) {
541      throw new IllegalArgumentException("Not file protocol");
542    }
543    try {
544      return new File(url.toURI());
545    } catch(URISyntaxException e) {
546      return new File(url.getPath());
547    }
548  }
549
550  public static Class<?> resolveToClass(Type implementation, Class<?> type, int parameterIndex) {
551    if (implementation == null) {
552      throw new NullPointerException("No null type accepted");
553    }
554
555    // First resolve to type
556    Type resolvedType = resolve(implementation, type, parameterIndex);
557
558    //
559    if (resolvedType != null) {
560      return resolveToClass(resolvedType);
561    } else {
562      return null;
563    }
564  }
565
566  public static Class resolveToClass(Type type) {
567    if (type == null) {
568      throw new NullPointerException("No null type accepted");
569    }
570    if (type instanceof Class<?>) {
571      return (Class<?>)type;
572    } else if (type instanceof TypeVariable) {
573      TypeVariable resolvedTypeVariable = (TypeVariable)type;
574      return resolveToClass(resolvedTypeVariable.getBounds()[0]);
575    } else {
576      throw new UnsupportedOperationException("Type resolution of " + type + " not yet implemented");
577    }
578  }
579
580  /**
581   * A simplistic implementation, it may not handle all cases but it should handle enough.
582   *
583   * @param implementation the type for which the parameter requires a resolution
584   * @param type the type that owns the parameter
585   * @param parameterIndex the parameter index
586   * @return the resolved type
587   */
588  public static Type resolve(Type implementation, Class<?> type, int parameterIndex) {
589    if (implementation == null) {
590      throw new NullPointerException();
591    }
592
593    //
594    if (implementation == type) {
595      TypeVariable<? extends Class<?>>[] tp = type.getTypeParameters();
596      if (parameterIndex < tp.length) {
597        return tp[parameterIndex];
598      } else {
599        throw new IllegalArgumentException();
600      }
601    } else if (implementation instanceof Class<?>) {
602      Class<?> c = (Class<?>) implementation;
603      Type gsc = c.getGenericSuperclass();
604      Type resolved = null;
605      if (gsc != null) {
606        resolved = resolve(gsc, type, parameterIndex);
607        if (resolved == null) {
608          // Try with interface
609        }
610      }
611      return resolved;
612    } else if (implementation instanceof ParameterizedType) {
613      ParameterizedType pt = (ParameterizedType) implementation;
614      Type[] typeArgs = pt.getActualTypeArguments();
615      Type rawType = pt.getRawType();
616      if (rawType == type) {
617        return typeArgs[parameterIndex];
618      } else if (rawType instanceof Class<?>) {
619        Class<?> classRawType = (Class<?>)rawType;
620        Type resolved = resolve(classRawType, type, parameterIndex);
621        if (resolved == null) {
622          return null;
623        } else if (resolved instanceof TypeVariable) {
624          TypeVariable resolvedTV = (TypeVariable)resolved;
625          TypeVariable[] a = classRawType.getTypeParameters();
626          for (int i = 0;i < a.length;i++) {
627            if (a[i].equals(resolvedTV)) {
628              return resolve(implementation, classRawType, i);
629            }
630          }
631          throw new AssertionError();
632        } else {
633          throw new UnsupportedOperationException("Cannot support resolution of " + resolved);
634        }
635      } else {
636        throw new UnsupportedOperationException();
637      }
638    } else {
639      throw new UnsupportedOperationException("todo " + implementation + " " + implementation.getClass());
640    }
641  }
642
643  public static boolean instanceOf(Class c, List<String> types) {
644
645    for (String type: types) {
646      if (instanceOf(c, type)) {
647        return true;
648      }
649    }
650
651    return false;
652
653  }
654
655  public static boolean instanceOf(Class c, String type) {
656
657    if (c.getName().equals(type)) {
658      return true;
659    }
660
661    for (Class i : c.getInterfaces()) {
662      if (instanceOf(i, type)) {
663        return true;
664      }
665    }
666
667    if (c.getSuperclass() != null) {
668      return instanceOf(c.getSuperclass(), type);
669    }
670
671    return false;
672  }
673
674  /** . */
675  private static final int ESCAPE = -1, SCANNING = 0, DOLLAR = 1, EVALUATING = 2;
676
677  /**
678   * Interpolate a string and replace the occurence from a context map, the syntax for a variable
679   * is <code>${}</code> and it can accept a default value used when the variable cannot be resolved
680   * with the <code>:-</code> separator:
681   *
682   * <ul>
683   *   <li><code>{}</code> + "foo" => "foo"</li>
684   *   <li><code>{}</code> + "${foo}" => ""</li>
685   *   <li><code>{}</code> + "\\${foo}" => "${foo}"</li>
686   *   <li><code>{foo:bar}</code> + "${foo}" => "bar"</li>
687   *   <li><code>{}</code> + "${foo:-bar}" => "bar"</li>
688   * </ul>
689   *
690   * @param interpolated the value to interpolate
691   * @param context the context
692   * @return the interpolated string
693   * @throws NullPointerException if the interpolated argument is null
694   */
695  public static String interpolate(String interpolated, Map<?, ?> context) throws NullPointerException {
696    StringBuilder sb = new StringBuilder();
697    int status = 0;
698    int prev = 0;
699    int length = interpolated.length();
700    for (int i = 0;i < length;i++) {
701      char c = interpolated.charAt(i);
702      switch (status) {
703        case ESCAPE:
704          if (c == '$') {
705            sb.append('$');
706          } else {
707            sb.append('\\').append(c);
708          }
709          status = SCANNING;
710          break;
711        case SCANNING:
712          if (c == '$') {
713            status = DOLLAR;
714          } else if (c == '\\') {
715            status = ESCAPE;
716          } else {
717            sb.append(c);
718          }
719          break;
720        case DOLLAR:
721          if (c == '{') {
722            status = EVALUATING;
723            prev = i + 1;
724          } else {
725            sb.append('$').append(c);
726          }
727          break;
728        case EVALUATING:
729          if (c == '}') {
730            int j = prev + 1;
731            while (j < i) {
732              if (j < length && interpolated.charAt(j - 1) == ':' && interpolated.charAt(j) == '-') {
733                break;
734              } else {
735                j++;
736              }
737            }
738            Object value;
739            if (j < i) {
740              String key = interpolated.substring(prev, j - 1);
741              value = context.get(key);
742              if (value == null) {
743                value = interpolated.substring(j + 1, i);
744              }
745            } else {
746              String key = interpolated.substring(prev, i);
747              value = context.get(key);
748            }
749            if (value != null) {
750              sb.append(value);
751            }
752            status = SCANNING;
753          }
754          break;
755      }
756    }
757    switch (status) {
758      case DOLLAR:
759        sb.append('$');
760        break;
761      case EVALUATING:
762        sb.append("${").append(interpolated, prev, interpolated.length());
763        break;
764    }
765    return sb.toString();
766  }
767
768  /**
769   * @return the current user directory
770   */
771  public static File getCurrentDirectory() {
772    String userDir = System.getProperty("user.dir");
773    return new File(userDir);
774  }
775
776  /** . */
777  public static final Charset UTF_8;
778
779  static {
780    UTF_8 = Charset.forName("UTF-8");
781  }
782
783}