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

org.robolectric.shadows.LegacyManifestParser Maven / Gradle / Ivy

package org.robolectric.shadows;

import static android.content.pm.ApplicationInfo.FLAG_ALLOW_BACKUP;
import static android.content.pm.ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA;
import static android.content.pm.ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING;
import static android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE;
import static android.content.pm.ApplicationInfo.FLAG_HAS_CODE;
import static android.content.pm.ApplicationInfo.FLAG_KILL_AFTER_RESTORE;
import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT;
import static android.content.pm.ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS;
import static android.content.pm.ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS;
import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS;
import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
import static android.content.pm.ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS;
import static android.content.pm.ApplicationInfo.FLAG_TEST_ONLY;
import static android.content.pm.ApplicationInfo.FLAG_VM_SAFE_MODE;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.PatternMatcher.PATTERN_LITERAL;
import static android.os.PatternMatcher.PATTERN_PREFIX;
import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB;
import static java.util.Arrays.asList;

import android.content.IntentFilter.MalformedMimeTypeException;
import android.content.pm.ActivityInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.Activity;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser.IntentInfo;
import android.content.pm.PackageParser.Package;
import android.content.pm.PackageParser.Permission;
import android.content.pm.PackageParser.PermissionGroup;
import android.content.pm.PackageParser.Service;
import android.content.pm.PackageParser.ServiceIntentInfo;
import android.content.pm.PathPermission;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Process;
import android.util.Pair;
import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.manifest.ActivityData;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.manifest.BroadcastReceiverData;
import org.robolectric.manifest.ContentProviderData;
import org.robolectric.manifest.IntentFilterData;
import org.robolectric.manifest.IntentFilterData.DataAuthority;
import org.robolectric.manifest.PackageItemData;
import org.robolectric.manifest.PathPermissionData;
import org.robolectric.manifest.PermissionGroupItemData;
import org.robolectric.manifest.PermissionItemData;
import org.robolectric.manifest.ServiceData;
import org.robolectric.res.AttributeResource;
import org.robolectric.res.ResName;
import org.robolectric.util.ReflectionHelpers;

/** Creates a {@link PackageInfo} from a {@link AndroidManifest} */
@SuppressWarnings("NewApi")
public class LegacyManifestParser {

  private static final List> APPLICATION_FLAGS =
      asList(
          Pair.create("android:allowBackup", FLAG_ALLOW_BACKUP),
          Pair.create("android:allowClearUserData", FLAG_ALLOW_CLEAR_USER_DATA),
          Pair.create("android:allowTaskReparenting", FLAG_ALLOW_TASK_REPARENTING),
          Pair.create("android:debuggable", FLAG_DEBUGGABLE),
          Pair.create("android:hasCode", FLAG_HAS_CODE),
          Pair.create("android:killAfterRestore", FLAG_KILL_AFTER_RESTORE),
          Pair.create("android:persistent", FLAG_PERSISTENT),
          Pair.create("android:resizeable", FLAG_RESIZEABLE_FOR_SCREENS),
          Pair.create("android:restoreAnyVersion", FLAG_RESTORE_ANY_VERSION),
          Pair.create("android:largeScreens", FLAG_SUPPORTS_LARGE_SCREENS),
          Pair.create("android:normalScreens", FLAG_SUPPORTS_NORMAL_SCREENS),
          Pair.create("android:anyDensity", FLAG_SUPPORTS_SCREEN_DENSITIES),
          Pair.create("android:smallScreens", FLAG_SUPPORTS_SMALL_SCREENS),
          Pair.create("android:testOnly", FLAG_TEST_ONLY),
          Pair.create("android:vmSafeMode", FLAG_VM_SAFE_MODE));
  private static final List> CONFIG_OPTIONS =
      asList(
          Pair.create("mcc", ActivityInfo.CONFIG_MCC),
          Pair.create("mnc", ActivityInfo.CONFIG_MNC),
          Pair.create("locale", ActivityInfo.CONFIG_LOCALE),
          Pair.create("touchscreen", ActivityInfo.CONFIG_TOUCHSCREEN),
          Pair.create("keyboard", ActivityInfo.CONFIG_KEYBOARD),
          Pair.create("keyboardHidden", ActivityInfo.CONFIG_KEYBOARD_HIDDEN),
          Pair.create("navigation", ActivityInfo.CONFIG_NAVIGATION),
          Pair.create("screenLayout", ActivityInfo.CONFIG_SCREEN_LAYOUT),
          Pair.create("fontScale", ActivityInfo.CONFIG_FONT_SCALE),
          Pair.create("uiMode", ActivityInfo.CONFIG_UI_MODE),
          Pair.create("orientation", ActivityInfo.CONFIG_ORIENTATION),
          Pair.create("screenSize", ActivityInfo.CONFIG_SCREEN_SIZE),
          Pair.create("smallestScreenSize", ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE));

  public static Package createPackage(AndroidManifest androidManifest) {

    Package pkg = new Package(androidManifest.getPackageName());

    pkg.mVersionName = androidManifest.getVersionName();
    pkg.mVersionCode = androidManifest.getVersionCode();

    Map permissionItemData = androidManifest.getPermissions();
    for (PermissionItemData itemData : permissionItemData.values()) {
      Permission permission = new Permission(pkg, createPermissionInfo(pkg, itemData));
      permission.metaData = permission.info.metaData;
      pkg.permissions.add(permission);
    }

    Map permissionGroupItemData =
        androidManifest.getPermissionGroups();
    for (PermissionGroupItemData itemData : permissionGroupItemData.values()) {
      PermissionGroup permissionGroup =
          new PermissionGroup(pkg, createPermissionGroupInfo(pkg, itemData));
      permissionGroup.metaData = permissionGroup.info.metaData;
      pkg.permissionGroups.add(permissionGroup);
    }

    pkg.requestedPermissions.addAll(androidManifest.getUsedPermissions());
    if (RuntimeEnvironment.getApiLevel() < VERSION_CODES.M) {
      List permissionsRequired =
          ReflectionHelpers.getField(pkg, "requestedPermissionsRequired");
      permissionsRequired.addAll(buildBooleanList(pkg.requestedPermissions.size(), true));
    }

    pkg.applicationInfo.flags = decodeFlags(androidManifest.getApplicationAttributes());
    pkg.applicationInfo.targetSdkVersion = androidManifest.getTargetSdkVersion();
    pkg.applicationInfo.packageName = androidManifest.getPackageName();
    pkg.applicationInfo.processName = androidManifest.getProcessName();
    if (!Strings.isNullOrEmpty(androidManifest.getApplicationName())) {
      pkg.applicationInfo.className =
          buildClassName(pkg.applicationInfo.packageName, androidManifest.getApplicationName());
      if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.N_MR1) {
        pkg.applicationInfo.name = pkg.applicationInfo.className;
      }
    }
    pkg.applicationInfo.metaData = metaDataToBundle(androidManifest.getApplicationMetaData());
    pkg.applicationInfo.uid = Process.myUid();
    if (androidManifest.getThemeRef() != null) {
      pkg.applicationInfo.theme =
          RuntimeEnvironment.getAppResourceTable()
              .getResourceId(
                  ResName.qualifyResName(
                      androidManifest.getThemeRef().replace("@", ""), pkg.packageName, "style"));
    }

    int labelRes = 0;
    if (androidManifest.getLabelRef() != null) {
      String fullyQualifiedName =
          ResName.qualifyResName(androidManifest.getLabelRef(), androidManifest.getPackageName());
      Integer id =
          fullyQualifiedName == null
              ? null
              : RuntimeEnvironment.getAppResourceTable()
                  .getResourceId(new ResName(fullyQualifiedName));
      labelRes = id != null ? id : 0;
    }

    pkg.applicationInfo.labelRes = labelRes;
    String labelRef = androidManifest.getLabelRef();
    if (labelRef != null && !labelRef.startsWith("@")) {
      pkg.applicationInfo.nonLocalizedLabel = labelRef;
    }

    Map activityDatas = androidManifest.getActivityDatas();
    for (ActivityData data : activityDatas.values()) {
      ActivityInfo activityInfo = new ActivityInfo();
      activityInfo.name = buildClassName(pkg.packageName, data.getName());
      activityInfo.packageName = pkg.packageName;
      activityInfo.configChanges = getConfigChanges(data);
      activityInfo.parentActivityName = data.getParentActivityName();
      activityInfo.metaData = metaDataToBundle(data.getMetaData().getValueMap());
      activityInfo.applicationInfo = pkg.applicationInfo;
      activityInfo.targetActivity = data.getTargetActivityName();
      activityInfo.exported = data.isExported();
      activityInfo.permission = data.getPermission();
      activityInfo.enabled = data.isEnabled();
      String themeRef;

      // Based on ShadowActivity
      if (data.getThemeRef() != null) {
        themeRef = data.getThemeRef();
      } else {
        themeRef = androidManifest.getThemeRef();
      }
      if (themeRef != null) {
        activityInfo.theme =
            RuntimeEnvironment.getAppResourceTable()
                .getResourceId(
                    ResName.qualifyResName(themeRef.replace("@", ""), pkg.packageName, "style"));
      }

      if (data.getLabel() != null) {
        activityInfo.labelRes =
            RuntimeEnvironment.getAppResourceTable()
                .getResourceId(
                    ResName.qualifyResName(
                        data.getLabel().replace("@", ""), pkg.packageName, "string"));
        if (activityInfo.labelRes == 0) {
          activityInfo.nonLocalizedLabel = data.getLabel();
        }
      }

      Activity activity = createActivity(pkg, activityInfo);
      for (IntentFilterData intentFilterData : data.getIntentFilters()) {
        ActivityIntentInfo outInfo = new ActivityIntentInfo(activity);
        populateIntentInfo(intentFilterData, outInfo);
        activity.intents.add(outInfo);
      }
      pkg.activities.add(activity);
    }

    for (ContentProviderData data : androidManifest.getContentProviders()) {
      ProviderInfo info = new ProviderInfo();
      populateComponentInfo(info, pkg, data);
      info.authority = data.getAuthorities();

      List permissions = new ArrayList<>();
      for (PathPermissionData permissionData : data.getPathPermissionDatas()) {
        permissions.add(createPathPermission(permissionData));
      }
      info.pathPermissions = permissions.toArray(new PathPermission[permissions.size()]);
      info.readPermission = data.getReadPermission();
      info.writePermission = data.getWritePermission();
      info.grantUriPermissions = data.getGrantUriPermissions();
      info.enabled = data.isEnabled();
      pkg.providers.add(createProvider(pkg, info));
    }

    for (BroadcastReceiverData data : androidManifest.getBroadcastReceivers()) {
      ActivityInfo info = new ActivityInfo();
      populateComponentInfo(info, pkg, data);
      info.permission = data.getPermission();
      info.exported = data.isExported();
      info.enabled = data.isEnabled();
      Activity receiver = createActivity(pkg, info);
      for (IntentFilterData intentFilterData : data.getIntentFilters()) {
        ActivityIntentInfo outInfo = new ActivityIntentInfo(receiver);
        populateIntentInfo(intentFilterData, outInfo);
        receiver.intents.add(outInfo);
      }
      pkg.receivers.add(receiver);
    }

    for (ServiceData data : androidManifest.getServices()) {
      ServiceInfo info = new ServiceInfo();
      populateComponentInfo(info, pkg, data);
      info.permission = data.getPermission();
      info.exported = data.isExported();
      info.enabled = data.isEnabled();

      Service service = createService(pkg, info);
      for (IntentFilterData intentFilterData : data.getIntentFilters()) {
        ServiceIntentInfo outInfo = new ServiceIntentInfo(service);
        populateIntentInfo(intentFilterData, outInfo);
        service.intents.add(outInfo);
      }
      pkg.services.add(service);
    }

    String codePath = RuntimeEnvironment.getTempDirectory()
        .createIfNotExists(pkg.packageName + "-codePath")
        .toAbsolutePath()
        .toString();
    if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) {
      pkg.codePath = codePath;
    } else {
      ReflectionHelpers.setField(Package.class, pkg, "mPath", codePath);
    }

    return pkg;
  }

  private static PathPermission createPathPermission(PathPermissionData data) {
    if (!Strings.isNullOrEmpty(data.pathPattern)) {
      return new PathPermission(
          data.pathPattern, PATTERN_SIMPLE_GLOB, data.readPermission, data.writePermission);
    } else if (!Strings.isNullOrEmpty(data.path)) {
      return new PathPermission(
          data.path, PATTERN_LITERAL, data.readPermission, data.writePermission);
    } else if (!Strings.isNullOrEmpty(data.pathPrefix)) {
      return new PathPermission(
          data.pathPrefix, PATTERN_PREFIX, data.readPermission, data.writePermission);
    } else {
      throw new IllegalStateException("Permission without type");
    }
  }

  private static void populateComponentInfo(
      ComponentInfo outInfo, Package owner, PackageItemData itemData) {
    populatePackageItemInfo(outInfo, owner, itemData);
    outInfo.applicationInfo = owner.applicationInfo;
  }

  private static void populatePackageItemInfo(
      PackageItemInfo outInfo, Package owner, PackageItemData itemData) {
    outInfo.name = buildClassName(owner.packageName, itemData.getName());
    outInfo.packageName = owner.packageName;
    outInfo.metaData = metaDataToBundle(itemData.getMetaData().getValueMap());
  }

  private static List buildBooleanList(int size, boolean defaultVal) {
    Boolean[] barray = new Boolean[size];
    Arrays.fill(barray, defaultVal);
    return Arrays.asList(barray);
  }

  private static PackageParser.Provider createProvider(Package pkg, ProviderInfo info) {
    PackageParser.Provider provider =
        ReflectionHelpers.callConstructor(PackageParser.Provider.class);
    populateComponent(pkg, info, provider);
    return provider;
  }

  private static Activity createActivity(Package pkg, ActivityInfo activityInfo) {
    Activity activity = ReflectionHelpers.callConstructor(Activity.class);
    populateComponent(pkg, activityInfo, activity);
    return activity;
  }

  private static Service createService(Package pkg, ServiceInfo info) {
    PackageParser.Service service = ReflectionHelpers.callConstructor(PackageParser.Service.class);
    populateComponent(pkg, info, service);
    return service;
  }

  private static void populateComponent(
      Package pkg, ComponentInfo info, PackageParser.Component component) {
    ReflectionHelpers.setField(component, "info", info);
    ReflectionHelpers.setField(component, "intents", new ArrayList<>());
    ReflectionHelpers.setField(component, "owner", pkg);
    ReflectionHelpers.setField(component, "className", info.name);
  }

  private static void populateIntentInfo(IntentFilterData intentFilterData, IntentInfo outInfo) {
    for (String action : intentFilterData.getActions()) {
      outInfo.addAction(action);
    }
    for (String category : intentFilterData.getCategories()) {
      outInfo.addCategory(category);
    }
    for (DataAuthority dataAuthority : intentFilterData.getAuthorities()) {
      outInfo.addDataAuthority(dataAuthority.getHost(), dataAuthority.getPort());
    }
    for (String mimeType : intentFilterData.getMimeTypes()) {
      try {
        outInfo.addDataType(mimeType);
      } catch (MalformedMimeTypeException e) {
        throw new RuntimeException(e);
      }
    }
    for (String scheme : intentFilterData.getSchemes()) {
      outInfo.addDataScheme(scheme);
    }
    for (String pathPattern : intentFilterData.getPathPatterns()) {
      outInfo.addDataPath(pathPattern, PATTERN_SIMPLE_GLOB);
    }
    for (String pathPattern : intentFilterData.getPathPrefixes()) {
      outInfo.addDataPath(pathPattern, PATTERN_PREFIX);
    }
    for (String pathPattern : intentFilterData.getPaths()) {
      outInfo.addDataPath(pathPattern, PATTERN_LITERAL);
    }
  }

  private static int getConfigChanges(ActivityData activityData) {
    String s = activityData.getConfigChanges();

    int res = 0;

    // quick sanity check.
    if (s == null || "".equals(s)) {
      return res;
    }

    String[] pieces = s.split("\\|", 0);

    for (String s1 : pieces) {
      s1 = s1.trim();

      for (Pair pair : CONFIG_OPTIONS) {
        if (s1.equals(pair.first)) {
          res |= pair.second;
          break;
        }
      }
    }

    // Matches platform behavior
    if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.O) {
      res |= ActivityInfo.CONFIG_MNC;
      res |= ActivityInfo.CONFIG_MCC;
    }

    return res;
  }

  private static int decodeFlags(Map applicationAttributes) {
    int applicationFlags = 0;
    for (Pair pair : APPLICATION_FLAGS) {
      if ("true".equals(applicationAttributes.get(pair.first))) {
        applicationFlags |= pair.second;
      }
    }
    return applicationFlags;
  }

  private static PermissionInfo createPermissionInfo(Package owner, PermissionItemData itemData) {
    PermissionInfo permissionInfo = new PermissionInfo();
    populatePackageItemInfo(permissionInfo, owner, itemData);

    permissionInfo.group = itemData.getPermissionGroup();
    permissionInfo.protectionLevel = decodeProtectionLevel(itemData.getProtectionLevel());
    permissionInfo.metaData = metaDataToBundle(itemData.getMetaData().getValueMap());

    String descriptionRef = itemData.getDescription();
    if (descriptionRef != null) {
      ResName descResName =
          AttributeResource.getResourceReference(descriptionRef, owner.packageName, "string");
      permissionInfo.descriptionRes =
          RuntimeEnvironment.getAppResourceTable().getResourceId(descResName);
    }

    String labelRefOrString = itemData.getLabel();
    if (labelRefOrString != null) {
      if (AttributeResource.isResourceReference(labelRefOrString)) {
        ResName labelResName =
            AttributeResource.getResourceReference(labelRefOrString, owner.packageName, "string");
        permissionInfo.labelRes =
            RuntimeEnvironment.getAppResourceTable().getResourceId(labelResName);
      } else {
        permissionInfo.nonLocalizedLabel = labelRefOrString;
      }
    }

    return permissionInfo;
  }

  private static PermissionGroupInfo createPermissionGroupInfo(Package owner,
      PermissionGroupItemData itemData) {
    PermissionGroupInfo permissionGroupInfo = new PermissionGroupInfo();
    populatePackageItemInfo(permissionGroupInfo, owner, itemData);

    permissionGroupInfo.metaData = metaDataToBundle(itemData.getMetaData().getValueMap());

    String descriptionRef = itemData.getDescription();
    if (descriptionRef != null) {
      ResName descResName =
          AttributeResource.getResourceReference(descriptionRef, owner.packageName, "string");
      permissionGroupInfo.descriptionRes =
          RuntimeEnvironment.getAppResourceTable().getResourceId(descResName);
    }

    String labelRefOrString = itemData.getLabel();
    if (labelRefOrString != null) {
      if (AttributeResource.isResourceReference(labelRefOrString)) {
        ResName labelResName =
            AttributeResource.getResourceReference(labelRefOrString, owner.packageName, "string");
        permissionGroupInfo.labelRes =
            RuntimeEnvironment.getAppResourceTable().getResourceId(labelResName);
      } else {
        permissionGroupInfo.nonLocalizedLabel = labelRefOrString;
      }
    }

    return permissionGroupInfo;
  }

  private static int decodeProtectionLevel(String protectionLevel) {
    if (protectionLevel == null) {
      return PermissionInfo.PROTECTION_NORMAL;
    }

    int permissions = PermissionInfo.PROTECTION_NORMAL;
    String[] levels = protectionLevel.split("\\|", 0);

    for (String level : levels) {
      switch (level) {
        case "normal":
          permissions |= PermissionInfo.PROTECTION_NORMAL;
          break;
        case "dangerous":
          permissions |= PermissionInfo.PROTECTION_DANGEROUS;
          break;
        case "signature":
          permissions |= PermissionInfo.PROTECTION_SIGNATURE;
          break;
        case "signatureOrSystem":
          permissions |= PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM;
          break;
        case "privileged":
          permissions |= PermissionInfo.PROTECTION_FLAG_PRIVILEGED;
          break;
        case "system":
          permissions |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
          break;
        case "development":
          permissions |= PermissionInfo.PROTECTION_FLAG_DEVELOPMENT;
          break;
        case "appop":
          permissions |= PermissionInfo.PROTECTION_FLAG_APPOP;
          break;
        case "pre23":
          permissions |= PermissionInfo.PROTECTION_FLAG_PRE23;
          break;
        case "installer":
          permissions |= PermissionInfo.PROTECTION_FLAG_INSTALLER;
          break;
        case "verifier":
          permissions |= PermissionInfo.PROTECTION_FLAG_VERIFIER;
          break;
        case "preinstalled":
          permissions |= PermissionInfo.PROTECTION_FLAG_PREINSTALLED;
          break;
        case "setup":
          permissions |= PermissionInfo.PROTECTION_FLAG_SETUP;
          break;
        case "instant":
          permissions |= PermissionInfo.PROTECTION_FLAG_INSTANT;
          break;
        case "runtime":
          permissions |= PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY;
          break;
        case "oem":
          permissions |= PermissionInfo.PROTECTION_FLAG_OEM;
          break;
        case "vendorPrivileged":
          permissions |= PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED;
          break;
        case "textClassifier":
          permissions |= PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER;
          break;
        default:
          throw new IllegalArgumentException("unknown protection level " + protectionLevel);
      }
    }
    return permissions;
  }

  /**
   * Goes through the meta data and puts each value in to a bundle as the correct type.
   *
   * 

Note that this will convert resource identifiers specified via the value attribute as well. * * @param meta Meta data to put in to a bundle * @return bundle containing the meta data */ private static Bundle metaDataToBundle(Map meta) { if (meta.size() == 0) { return null; } Bundle bundle = new Bundle(); for (Map.Entry entry : meta.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (Boolean.class.isInstance(value)) { bundle.putBoolean(key, (Boolean) value); } else if (Float.class.isInstance(value)) { bundle.putFloat(key, (Float) value); } else if (Integer.class.isInstance(value)) { bundle.putInt(key, (Integer) value); } else { bundle.putString(key, value == null ? null : value.toString()); } } return bundle; } private static String buildClassName(String pkg, String cls) { if (Strings.isNullOrEmpty(cls)) { throw new IllegalArgumentException("Empty class name in package " + pkg); } char c = cls.charAt(0); if (c == '.') { return (pkg + cls).intern(); } if (cls.indexOf('.') < 0) { StringBuilder b = new StringBuilder(pkg); b.append('.'); b.append(cls); return b.toString(); } return cls; // TODO: consider reenabling this for stricter platform-complaint checking // if (c >= 'a' && c <= 'z') { // return cls; // } // throw new IllegalArgumentException("Bad class name " + cls + " in package " + pkg); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy