All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.html.js.tostring.JSToString Maven / Gradle / Ivy

/**
 * Copyright (C) 2014-2016 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.html.js.tostring;

import java.util.Collection;
import java.util.Map;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.lang.ClassHelper;
import com.helger.html.hc.IHCNode;
import com.helger.html.hc.render.HCRenderer;
import com.helger.html.js.CJS;
import com.helger.html.js.IHasJSCode;
import com.helger.html.js.JSMarshaller;
import com.helger.json.IJson;

public final class JSToString
{
  private static final Logger s_aLogger = LoggerFactory.getLogger (JSToString.class);

  private JSToString ()
  {}

  @Nullable
  private static JSType _autoDetectJSType (final Class  aClass)
  {
    if (ClassHelper.isStringClass (aClass))
      return JSType.STRING;
    if (ClassHelper.isCharacterClass (aClass))
      return JSType.STRING;
    if (ClassHelper.isBooleanClass (aClass))
      return JSType.BOOLEAN;
    if (ClassHelper.isFloatingPointClass (aClass))
      return JSType.DOUBLE;
    if (ClassHelper.isIntegerClass (aClass))
      return JSType.INT;
    if (ClassHelper.isArrayClass (aClass))
      return new JSArrayType (JSType.AUTO_DETECT);
    if (IHasJSCode.class.isAssignableFrom (aClass))
      return JSType.JS;
    if (Map.class.isAssignableFrom (aClass))
      return new JSMapType (JSType.AUTO_DETECT, JSType.AUTO_DETECT);
    if (Collection.class.isAssignableFrom (aClass))
      return new JSListType (JSType.AUTO_DETECT);
    if (IJson.class.isAssignableFrom (aClass))
      return JSType.JSON;
    s_aLogger.warn ("Failed to detect JS type of class " + aClass);
    return null;
  }

  @Nonnull
  private static JSType _getRealJSType (@Nullable final Object aObject, @Nonnull final JSType aSupposedType)
  {
    if (!aSupposedType.equals (JSType.AUTO_DETECT))
      return aSupposedType;

    if (aObject == null)
      return JSType.VOID;

    // auto-detect JS type!!!
    final JSType aRealType = _autoDetectJSType (aObject.getClass ());
    if (aRealType == null)
      throw new IllegalArgumentException ("Unsupported data type: " + aObject.getClass ());
    return aRealType;
  }

  private static void _toJSString (@Nullable final Object aObject,
                                   @Nonnull final JSType aType,
                                   @Nonnull final StringBuilder aSB,
                                   @Nonnegative final int nLevel,
                                   final boolean bWithSurroundingVar)
  {
    if (aObject == null)
      aSB.append (CJS.JS_NULL);
    else
    {
      switch (aType.getType ())
      {
        case BOOLEAN:
        case DOUBLE:
          // double: No check for "Number" because this destroys float values!
          aSB.append (aObject.toString ());
          break;
        case INT:
          if (aObject instanceof Number)
            aSB.append (Long.toString (((Number) aObject).longValue ()));
          else
            aSB.append (aObject.toString ());
          break;
        case HTML:
          if (aObject instanceof IHCNode)
            aSB.append (HCRenderer.getAsHTMLStringWithoutNamespaces ((IHCNode) aObject));
          else
            aSB.append ((String) aObject);
          break;
        case JS:
          // Use JS as is
          if (aObject instanceof IHasJSCode)
            aSB.append (((IHasJSCode) aObject).getJSCode ());
          else
            aSB.append ((String) aObject);
          break;
        case JSON:
          aSB.append (((IJson) aObject).getAsJsonString ());
          break;
        case STRING:
          // Note: use single quotes for use in HTML attributes!
          final String sValue = String.valueOf (aObject);
          aSB.append ('\'').append (JSMarshaller.javaScriptEscape (sValue)).append ('\'');
          break;
        case ARRAY:
        case LIST:
        {
          if (!(aType instanceof JSListType) && !(aType instanceof JSArrayType))
            throw new IllegalArgumentException ("object is not a list: " + aType);

          // get type of list elements
          final JSType aListType = ((IHasChildJSType) aType).getChildType ();

          // for all values (recursive)
          if (nLevel == 0 && bWithSurroundingVar)
            aSB.append ("var x=");
          aSB.append ('[');

          int i = 0;
          if (aType.getType () == EJSType.ARRAY)
          {
            // Handle arrays
            final Object [] aArray = (Object []) aObject;
            for (final Object aMember : aArray)
            {
              if (i++ > 0)
                aSB.append (',');
              _toJSString (aMember, _getRealJSType (aMember, aListType), aSB, nLevel + 1, bWithSurroundingVar);
            }
          }
          else
          {
            // Handle lists
            final Collection  aList = (Collection ) aObject;
            for (final Object aMember : aList)
            {
              if (i++ > 0)
                aSB.append (',');
              _toJSString (aMember, _getRealJSType (aMember, aListType), aSB, nLevel + 1, bWithSurroundingVar);
            }
          }
          aSB.append (']');
          if (nLevel == 0 && bWithSurroundingVar)
          {
            // JS "eval" should return the array!
            aSB.append (";x");
          }
          break;
        }
        case MAP:
        {
          if (!(aType instanceof JSMapType))
            throw new IllegalArgumentException ("object is not a map");

          final Map  aMap = (Map ) aObject;

          // get type of map elements
          final JSType aKeyType = ((JSMapType) aType).getKeyType ();
          final JSType aValueType = ((JSMapType) aType).getValueType ();

          // for all keys (recursive)
          if (nLevel == 0 && bWithSurroundingVar)
            aSB.append ("var x=");
          aSB.append ('{');

          int i = 0;
          for (final Map.Entry  aEntry : aMap.entrySet ())
          {
            if (i++ > 0)
              aSB.append (',');

            final Object aKey = aEntry.getKey ();
            final Object aValue = aEntry.getValue ();

            // append key and value
            _toJSString (aKey, _getRealJSType (aKey, aKeyType), aSB, nLevel + 1, bWithSurroundingVar);
            aSB.append (':');
            _toJSString (aValue, _getRealJSType (aValue, aValueType), aSB, nLevel + 1, bWithSurroundingVar);
          }
          aSB.append ('}');
          if (nLevel == 0 && bWithSurroundingVar)
          {
            // JS "eval" should return the array!
            aSB.append (";x");
          }
          break;
        }
        case VOID:
          // do nothing
          break;
        default:
          throw new IllegalStateException ("Unknown type: " + aType.getType ());
      }
    }
  }

  /**
   * Auto-detect the type of the passed object and convert it to a JS string. If
   * the type detection failed, an {@link IllegalArgumentException} is thrown.
   *
   * @param aObject
   *        The object to be converted. May be null. Note: works
   *        for atomic types and arrays, but not for collection types!
   * @return The string representation of the passed object.
   */
  @Nonnull
  public static String objectToJSString (@Nullable final Object aObject)
  {
    return objectToJSString (aObject, JSType.AUTO_DETECT, false);
  }

  @Nonnull
  public static String objectToJSString (@Nullable final Object aObject, @Nonnull final JSType aType)
  {
    return objectToJSString (aObject, aType, false);
  }

  @Nonnull
  public static String objectToJSString (@Nullable final Object aObject,
                                         @Nonnull final JSType aType,
                                         final boolean bWithSurroundingVar)
  {
    ValueEnforcer.notNull (aType, "Type");

    final StringBuilder aSB = new StringBuilder ();
    _toJSString (aObject, _getRealJSType (aObject, aType), aSB, 0, bWithSurroundingVar);
    return aSB.toString ();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy