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

org.robolectric.manifest.MetaData Maven / Gradle / Ivy

There is a newer version: 4.13
Show newest version
package org.robolectric.manifest;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.robolectric.res.ResName;
import org.robolectric.res.ResourceTable;
import org.robolectric.res.TypedResource;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

public final class MetaData {
  private final Map valueMap = new LinkedHashMap<>();
  private final Map typeMap = new LinkedHashMap<>();
  private boolean initialised;

  public MetaData(List nodes) {
    for (Node metaNode : nodes) {
      NamedNodeMap attributes = metaNode.getAttributes();
      Node nameAttr = attributes.getNamedItem("android:name");
      Node valueAttr = attributes.getNamedItem("android:value");
      Node resourceAttr = attributes.getNamedItem("android:resource");

      if (valueAttr != null) {
        valueMap.put(nameAttr.getNodeValue(), valueAttr.getNodeValue());
        typeMap.put(nameAttr.getNodeValue(), VALUE_TYPE.VALUE);
      } else if (resourceAttr != null) {
        valueMap.put(nameAttr.getNodeValue(), resourceAttr.getNodeValue());
        typeMap.put(nameAttr.getNodeValue(), VALUE_TYPE.RESOURCE);
      }
    }
  }

  public void init(ResourceTable resourceTable, String packageName) throws RoboNotFoundException {
    if (!initialised) {
      for (Map.Entry entry : typeMap.entrySet()) {
        String value = valueMap.get(entry.getKey()).toString();
        if (value.startsWith("@")) {
          ResName resName = ResName.qualifyResName(value.substring(1), packageName, null);

          switch (entry.getValue()) {
            case RESOURCE:
              // Was provided by resource attribute, store resource ID
              valueMap.put(entry.getKey(), resourceTable.getResourceId(resName));
              break;
            case VALUE:
              // Was provided by value attribute, need to inferFromValue it
              TypedResource typedRes = resourceTable.getValue(resName, "");
              // The typed resource's data is always a String, so need to inferFromValue the value.
              if (typedRes == null) {
                throw new RoboNotFoundException(resName.getFullyQualifiedName());
              }
              switch (typedRes.getResType()) {
                case BOOLEAN: case COLOR: case INTEGER: case FLOAT:
                  valueMap.put(entry.getKey(), parseValue(typedRes.getData().toString()));
                  break;
                default:
                  valueMap.put(entry.getKey(),typedRes.getData());
              }
              break;
          }
        } else if (entry.getValue() == VALUE_TYPE.VALUE) {
          // Raw value, so inferFromValue it in to the appropriate type and store it
          valueMap.put(entry.getKey(), parseValue(value));
        }
      }
      // Finished parsing, mark as initialised
      initialised = true;
    }
  }

  public Map getValueMap() {
    return valueMap;
  }

  private enum VALUE_TYPE {
    RESOURCE,
    VALUE
  }

  private Object parseValue(String value) {
    if (value == null) {
      return null;
    } else if ("true".equals(value)) {
      return true;
    } else if ("false".equals(value)) {
      return false;
    } else if (value.startsWith("#")) {
      // if it's a color, add it and continue
      try {
        return getColor(value);
      } catch (NumberFormatException e) {
            /* Not a color */
      }
    } else if (value.contains(".")) {
      // most likely a float
      try {
        return Float.parseFloat(value);
      } catch (NumberFormatException e) {
        // Not a float
      }
    } else {
      // if it's an int, add it and continue
      try {
        return Integer.parseInt(value);
      } catch (NumberFormatException ei) {
        // Not an int
      }
    }

    // Not one of the above types, keep as String
    return value;
  }

  // todo: this is copied from ResourceHelper, dedupe
  /**
   * Returns the color value represented by the given string value
   * @param value the color value
   * @return the color as an int
   * @throws NumberFormatException if the conversion failed.
   */
  public static int getColor(String value) {
    if (value != null) {
      if (value.startsWith("#") == false) {
        throw new NumberFormatException(
            String.format("Color value '%s' must start with #", value));
      }

      value = value.substring(1);

      // make sure it's not longer than 32bit
      if (value.length() > 8) {
        throw new NumberFormatException(String.format(
            "Color value '%s' is too long. Format is either" +
                "#AARRGGBB, #RRGGBB, #RGB, or #ARGB",
            value));
      }

      if (value.length() == 3) { // RGB format
        char[] color = new char[8];
        color[0] = color[1] = 'F';
        color[2] = color[3] = value.charAt(0);
        color[4] = color[5] = value.charAt(1);
        color[6] = color[7] = value.charAt(2);
        value = new String(color);
      } else if (value.length() == 4) { // ARGB format
        char[] color = new char[8];
        color[0] = color[1] = value.charAt(0);
        color[2] = color[3] = value.charAt(1);
        color[4] = color[5] = value.charAt(2);
        color[6] = color[7] = value.charAt(3);
        value = new String(color);
      } else if (value.length() == 6) {
        value = "FF" + value;
      }

      // this is a RRGGBB or AARRGGBB value

      // Integer.parseInt will fail to inferFromValue strings like "ff191919", so we use
      // a Long, but cast the result back into an int, since we know that we're only
      // dealing with 32 bit values.
      return (int)Long.parseLong(value, 16);
    }

    throw new NumberFormatException();
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy