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.cli.type;
021
022import org.crsh.cli.completers.EmptyCompleter;
023import org.crsh.cli.completers.EnumCompleter;
024import org.crsh.cli.completers.FileCompleter;
025import org.crsh.cli.completers.ObjectNameCompleter;
026import org.crsh.cli.completers.ThreadCompleter;
027import org.crsh.cli.spi.Completer;
028
029import javax.management.ObjectName;
030import java.io.File;
031import java.util.Properties;
032import java.util.StringTokenizer;
033
034/**
035 * Defines a type for values, this is used for transforming a textual value into a type, for command
036 * argument and options. A value type defines:
037 *
038 * <ul>
039 *   <li>The generic value type that is converted to.</li>
040 *   <li>The implementation of the {@link #parse(Class, String)} method that transforms the string into a value.</li>
041 *   <li>An optional completer.</li>
042 * </ul>
043 *
044 * @param <V> the generic value type
045 */
046public abstract class ValueType<V> {
047
048  /** Identity. */
049  public static final ValueType<String> STRING = new ValueType<String>(String.class) {
050    @Override
051    public <S extends String> S parse(Class<S> type, String s) throws Exception {
052      return type.cast(s);
053    }
054  };
055
056  /** Integer. */
057  public static final ValueType<Integer> INTEGER = new ValueType<Integer>(Integer.class) {
058    @Override
059    public <S extends Integer> S parse(Class<S> type, String s) throws Exception {
060      return type.cast(Integer.parseInt(s));
061    }
062  };
063
064  /** Boolean. */
065  public static final ValueType<Boolean> BOOLEAN = new ValueType<Boolean>(Boolean.class) {
066    @Override
067    public <S extends Boolean> S parse(Class<S> type, String s) throws Exception {
068      return type.cast(Boolean.parseBoolean(s));
069    }
070  };
071
072  /** Any Java enum. */
073  public static final ValueType<Enum> ENUM = new ValueType<Enum>(Enum.class, EnumCompleter.class) {
074    @Override
075    public <S extends Enum> S parse(Class<S> type, String s) {
076      // We cannot express S extends Enum<S> type
077      // so we need this necessary cast to make the java compiler happy
078      S s1 = (S)Enum.valueOf(type, s);
079      return s1;
080    }
081  };
082
083  /** Properties as semi colon separated values. */
084  public static final ValueType<Properties> PROPERTIES = new ValueType<Properties>(Properties.class) {
085    @Override
086    public <S extends Properties> S parse(Class<S> type, String s) throws Exception {
087      java.util.Properties props = new java.util.Properties();
088      StringTokenizer tokenizer = new StringTokenizer(s, ";", false);
089      while(tokenizer.hasMoreTokens()){
090        String token = tokenizer.nextToken();
091        if(token.contains("=")) {
092          String key = token.substring(0, token.indexOf('='));
093          String value = token.substring(token.indexOf('=') + 1, token.length());
094          props.put(key, value);
095        }
096      }
097      return type.cast(props);
098    }
099  };
100
101  /** A JMX object name value type. */
102  public static final ValueType<ObjectName> OBJECT_NAME = new ValueType<ObjectName>(ObjectName.class, ObjectNameCompleter.class) {
103    @Override
104    public <S extends ObjectName> S parse(Class<S> type, String s) throws Exception {
105      return type.cast(ObjectName.getInstance(s));
106    }
107  };
108
109  /** A value type for threads. */
110  public static final ValueType<Thread> THREAD = new ValueType<Thread>(Thread.class, ThreadCompleter.class) {
111    @Override
112    public <S extends Thread> S parse(Class<S> type, String s) throws Exception {
113      long id = Long.parseLong(s);
114      for (Thread t : Thread.getAllStackTraces().keySet()) {
115        if (t.getId() == id) {
116          return type.cast(t);
117        }
118      }
119      throw new IllegalArgumentException("No thread " + s );
120    }
121  };
122
123  /** A value type for files. */
124  public static final ValueType<File> FILE = new ValueType<File>(File.class, FileCompleter.class) {
125    @Override
126    public <S extends File> S parse(Class<S> type, String s) throws Exception {
127      return type.cast(new File(s));
128    }
129  };
130
131  /** . */
132  protected final Class<V> type;
133
134  /** . */
135  protected final Class<? extends Completer> completer;
136
137  protected ValueType(Class<V> type, Class<? extends Completer> completer) throws NullPointerException {
138    if (type == null) {
139      throw new NullPointerException("No null value type accepted");
140    }
141    if (completer == null) {
142      throw new NullPointerException("No null completer accepted");
143    }
144
145    //
146    this.completer = completer;
147    this.type = type;
148  }
149
150  protected ValueType(Class<V> type) throws NullPointerException {
151    if (type == null) {
152      throw new NullPointerException("No null value type accepted");
153    }
154
155    //
156    this.completer = EmptyCompleter.class;
157    this.type = type;
158  }
159
160  final int getDistance(Class<?> clazz) {
161    if (type == clazz) {
162      return 0;
163    } else if (type.isAssignableFrom(clazz)) {
164      int degree = 0;
165      for (Class<?> current = clazz;current != type;current = current.getSuperclass()) {
166        degree++;
167      }
168      return degree;
169    } else {
170      return -1;
171    }
172  }
173
174  @Override
175  public final int hashCode() {
176    return type.hashCode();
177  }
178
179  @Override
180  public final boolean equals(Object obj) {
181    if (obj == null) {
182      return false;
183    } else {
184      if (obj == this) {
185        return true;
186      } else {
187        if (obj.getClass() == ValueType.class) {
188          ValueType that = (ValueType)obj;
189          return type == that.type;
190        } else {
191          return false;
192        }
193      }
194    }
195  }
196
197  public Class<? extends Completer> getCompleter() {
198    return completer;
199  }
200
201  public final Class<V> getType() {
202    return type;
203  }
204
205  public final V parse(String s) throws Exception {
206    return parse(type, s);
207  }
208
209  /**
210   * Parse the <code>s</code> argument into a value of type S that is a subclass of the
211   * generic value type V.
212   *
213   * @param type the target type of the value
214   * @param s the string to convert
215   * @param <S> the generic type of the converted value
216   * @return the converted value
217   * @throws Exception any exception that would prevent the conversion to happen
218   */
219  public abstract <S extends V> S parse(Class<S> type, String s) throws Exception;
220
221}