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