content: /**
   * Copyright (c) 2012, Andy Janata
   * All rights reserved.
   * 
   * Redistribution and use in source and binary forms, with or without modification, are permitted
   * provided that the following conditions are met:
   * 
   * * Redistributions of source code must retain the above copyright notice, this list of conditions
   *   and the following disclaimer.
   * * Redistributions in binary form must reproduce the above copyright notice, this list of
   *   conditions and the following disclaimer in the documentation and/or other materials provided
   *   with the distribution.
   * 
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
   * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  package net.socialgamer.cah;
  
  import java.io.File;
  import java.io.PrintWriter;
  import java.lang.reflect.Field;
  import java.util.HashMap;
  import java.util.Map;
  
  import net.socialgamer.cah.Constants.DoubleLocalizable;
  import net.socialgamer.cah.Constants.Localizable;
  
  
  /**
   * Analyze the server enums using reflection and create a Javascript version for the client to use.
   * 
   * @author Andy Janata (ajanata@socialgamer.net)
   */
  public class UpdateJsConstants {
  
    private static final String enumHeaderFmt =
        "cah.$.%s = function() {\r\n  // Dummy constructor to make Eclipse auto-complete.\r\n};\r\n";
    private static final String enumDummyFmt =
        "cah.$.%s.prototype.dummyForAutocomplete = undefined;\r\n";
    private static final String enumValueFmt = "cah.$.%s.%s = \"%s\";\r\n";
    private static final String msgHeaderFmt = "cah.$.%s_msg = {};\r\n";
    private static final String msgValueFmt = "cah.$.%s_msg['%s'] = \"%s\";\r\n";
    private static final String msg2HeaderFmt = "cah.$.%s_msg_2 = {};\r\n";
    private static final String msg2ValueFmt = "cah.$.%s_msg_2['%s'] = \"%s\";\r\n";
  
    /**
     * Run the enum updater. The working directory for this program should be the project's root.
     * 
     * @param args
     */
    @SuppressWarnings("rawtypes")
    public static void main(final String[] args) throws Exception {
      final String dir = "WebContent/js/";
      final File outFile = new File(dir + "cah.constants.js");
      assert outFile.canWrite();
      assert outFile.delete();
      assert outFile.createNewFile();
      final PrintWriter writer = new PrintWriter(outFile);
  
      writer.println("// This file is automatically generated. Do not edit.");
      writer.println();
      writer.println("cah.$ = {};");
      writer.println();
  
      final Class[] classes = Constants.class.getClasses();
      for (final Class<?> c : classes) {
        // We only care about enums.
        if (!c.isEnum()) {
          continue;
        }
        // We need to know the name of the enum itself, not that it's Constants$Foo.
        final String cName = c.getName().split("\\$")[1];
        System.out.println(cName);
        writer.format(enumHeaderFmt, cName);
        writer.format(enumDummyFmt, cName);
        final Map<String, String> values = getEnumValues(c);
        for (final String key : values.keySet()) {
          final String value = values.get(key);
          writer.format(enumValueFmt, cName, key, value);
        }
        if (Localizable.class.isAssignableFrom(c) || DoubleLocalizable.class.isAssignableFrom(c)) {
          System.out.println(cName + "_msg");
          writer.format(msgHeaderFmt, cName);
          final Map<String, String> messages = getEnumMessageValues(c);
          for (final String key : messages.keySet()) {
            final String value = messages.get(key);
            writer.format(msgValueFmt, cName, key, value);
          }
        }
        if (DoubleLocalizable.class.isAssignableFrom(c)) {
          System.out.println(cName + "_msg_2");
          writer.format(msg2HeaderFmt, cName);
          final Map<String, String> messages = getEnumMessage2Values(c);
          for (final String key : messages.keySet()) {
            final String value = messages.get(key);
            writer.format(msg2ValueFmt, cName, key, value);
          }
        }
        writer.println();
      }
      writer.flush();
      writer.close();
    }
  
    /**
     * Return a map of enum values in an Enum class, with the enum field names as keys and the values
     * of toString() as the values.
     * 
     * @param enumClass
     *          The Enum to examine.
     * @return Map of field name -> toString values.
     * @throws IllegalArgumentException
     *           Thrown if {@code enumClass} isn't actually an enum.
     * @throws IllegalAccessException
     *           If the value was unable to be retrieved.
     */
    private static Map<String, String> getEnumValues(final Class<?> enumClass)
        throws IllegalArgumentException, IllegalAccessException {
      if (!enumClass.isEnum()) {
        throw new IllegalArgumentException(enumClass.getName() + " is not an enum");
      }
  
      final Field[] flds = enumClass.getDeclaredFields();
      final HashMap<String, String> enumMap = new HashMap<String, String>();
      for (final Field f : flds) {
        if (f.isEnumConstant()) {
          enumMap.put(f.getName(), f.get(null).toString());
        }
      }
      return enumMap;
    }
  
    /**
     * Return a map of {@code Localizable} message values in an Enum class, with the enum field names
     * as keys and the values of getString() as the values.
     * 
     * @param enumClass
     *          The Enum to examine.
     * @return Map of field name -> getString values.
     * @throws IllegalArgumentException
     *           Thrown if {@code enumClass} isn't actually an enum.
     * @throws IllegalAccessException
     *           If the value was unable to be retrieved.
     */
    private static Map<String, String> getEnumMessageValues(final Class<?> enumClass)
        throws IllegalArgumentException, IllegalAccessException {
      if (!enumClass.isEnum()) {
        throw new IllegalArgumentException(enumClass.getName() + " is not an enum");
      } else if (!Localizable.class.isAssignableFrom(enumClass)
          && !DoubleLocalizable.class.isAssignableFrom(enumClass)) {
        throw new IllegalArgumentException(enumClass.getName()
            + " does not implement Localizable or DoubleLocalizable.");
      }
  
      final Field[] flds = enumClass.getDeclaredFields();
      final HashMap<String, String> messageMap = new HashMap<String, String>();
      for (final Field f : flds) {
        if (f.isEnumConstant()) {
          if (Localizable.class.isAssignableFrom(enumClass)) {
            messageMap.put(f.get(null).toString(), ((Localizable) f.get(null)).getString());
          } else {
            messageMap.put(f.get(null).toString(), ((DoubleLocalizable) f.get(null)).getString());
          }
        }
      }
      return messageMap;
    }
  
    /**
     * Return a map of {@code DoubleLocalizable} message values in an Enum class, with the enum field
     * names as keys and the values of getString2() as the values.
     * 
     * @param enumClass
     *          The Enum to examine.
     * @return Map of field name -> getString2 values.
     * @throws IllegalArgumentException
     *           Thrown if {@code enumClass} isn't actually an enum.
     * @throws IllegalAccessException
     *           If the value was unable to be retrieved.
     */
    private static Map<String, String> getEnumMessage2Values(final Class<?> enumClass)
        throws IllegalArgumentException, IllegalAccessException {
      if (!enumClass.isEnum()) {
        throw new IllegalArgumentException(enumClass.getName() + " is not an enum");
      } else if (!DoubleLocalizable.class.isAssignableFrom(enumClass)) {
        throw new IllegalArgumentException(enumClass.getName()
            + " does not implement DoubleLocalizable.");
      }
  
      final Field[] flds = enumClass.getDeclaredFields();
      final HashMap<String, String> messageMap = new HashMap<String, String>();
      for (final Field f : flds) {
        if (f.isEnumConstant()) {
          messageMap.put(f.get(null).toString(), ((DoubleLocalizable) f.get(null)).getString2());
        }
      }
      return messageMap;
    }
  }
  