package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.KITKAT;

import android.accounts.Account;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.content.ContentProvider;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.PeriodicSync;
import android.content.SyncAdapterType;
import android.content.UriPermission;
import android.content.pm.ProviderInfo;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.fakes.BaseCursor;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.NamedStream;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;

@Implements(ContentResolver.class)
@SuppressLint("NewApi")
public class ShadowContentResolver {
  private int nextDatabaseIdForInserts;

  @RealObject ContentResolver realContentResolver;

  private BaseCursor cursor;
  private final List<Statement> statements = new ArrayList<>();
  private final List<InsertStatement> insertStatements = new ArrayList<>();
  private final List<UpdateStatement> updateStatements = new ArrayList<>();
  private final List<DeleteStatement> deleteStatements = new ArrayList<>();
  private List<NotifiedUri> notifiedUris = new ArrayList<>();
  private Map<Uri, BaseCursor> uriCursorMap = new HashMap<>();
  private Map<Uri, InputStream> inputStreamMap = new HashMap<>();
  private Map<Uri, OutputStream> outputStreamMap = new HashMap<>();
  private final Map<String, List<ContentProviderOperation>> contentProviderOperations =
      new HashMap<>();
  private ContentProviderResult[] contentProviderResults;
  private final List<UriPermission> uriPermissions = new ArrayList<>();

  private final CopyOnWriteArrayList<ContentObserverEntry> contentObservers =
      new CopyOnWriteArrayList<>();

  private static final Map<String, Map<Account, Status>> syncableAccounts = new HashMap<>();
  private static final Map<String, ContentProvider> providers = new HashMap<>();
  private static boolean masterSyncAutomatically;

  private static SyncAdapterType[] syncAdapterTypes;

  @Resetter
  public static synchronized void reset() {
    syncableAccounts.clear();
    providers.clear();
    masterSyncAutomatically = false;
  }

  private static class ContentObserverEntry {
    public final Uri uri;
    public final boolean notifyForDescendents;
    public final ContentObserver observer;

    private ContentObserverEntry(Uri uri, boolean notifyForDescendents, ContentObserver observer) {
      this.uri = uri;
      this.notifyForDescendents = notifyForDescendents;
      this.observer = observer;

      if (uri == null || observer == null) {
        throw new NullPointerException();
      }
    }

    public boolean matches(Uri test) {
      if (!Objects.equals(uri.getScheme(), test.getScheme())) {
        return false;
      }
      if (!Objects.equals(uri.getAuthority(), test.getAuthority())) {
        return false;
      }

      String uriPath = uri.getPath();
      String testPath = test.getPath();

      return Objects.equals(uriPath, testPath)
          || (notifyForDescendents && testPath != null && testPath.startsWith(uriPath));
    }
  }

  public static class NotifiedUri {
    public final Uri uri;
    public final boolean syncToNetwork;
    public final ContentObserver observer;

    public NotifiedUri(Uri uri, ContentObserver observer, boolean syncToNetwork) {
      this.uri = uri;
      this.syncToNetwork = syncToNetwork;
      this.observer = observer;
    }
  }

  public static class Status {
    public int syncRequests;
    public int state = -1;
    public boolean syncAutomatically;
    public Bundle syncExtras;
    public List<PeriodicSync> syncs = new ArrayList<>();
  }

  public void registerInputStream(Uri uri, InputStream inputStream) {
    inputStreamMap.put(uri, inputStream);
  }

  public void registerOutputStream(Uri uri, OutputStream outputStream) {
    outputStreamMap.put(uri, outputStream);
  }

  @Implementation
  protected final InputStream openInputStream(final Uri uri) {
    InputStream inputStream = inputStreamMap.get(uri);
    if (inputStream != null) {
      return inputStream;
    } else {
      return new UnregisteredInputStream(uri);
    }
  }

  @Implementation
  protected final OutputStream openOutputStream(final Uri uri) {
    OutputStream outputStream = outputStreamMap.get(uri);
    if (outputStream != null) {
      return outputStream;
    }

    return new OutputStream() {

      @Override
      public void write(int arg0) throws IOException {}

      @Override
      public String toString() {
        return "outputstream for " + uri;
      }
    };
  }

  /**
   * If a {@link ContentProvider} is registered for the given {@link Uri}, its {@link
   * ContentProvider#insert(Uri, ContentValues)} method will be invoked.
   *
   *
Tests can verify that this method was called using {@link #getStatements()} or {@link
   * #getInsertStatements()}.
   *
   *
If no appropriate {@link ContentProvider} is found, no action will be taken and a {@link
   * Uri} including the incremented value set with {@link #setNextDatabaseIdForInserts(int)} will
   * returned.
   */
  @Implementation
  protected final Uri insert(Uri url, ContentValues values) {
    ContentProvider provider = getProvider(url);
    ContentValues valuesCopy = (values == null) ? null : new ContentValues(values);
    InsertStatement insertStatement = new InsertStatement(url, provider, valuesCopy);
    statements.add(insertStatement);
    insertStatements.add(insertStatement);

    if (provider != null) {
      return provider.insert(url, values);
    } else {
      return Uri.parse(url.toString() + "/" + ++nextDatabaseIdForInserts);
    }
  }

  /**
   * If a {@link ContentProvider} is registered for the given {@link Uri}, its
   * {@link ContentProvider#update(Uri, ContentValues, String, String[])} method will be invoked.
   *
   * Tests can verify that this method was called using {@link #getStatements()} or
   * {@link #getUpdateStatements()}.
   *
   * @return If no appropriate {@link ContentProvider} is found, no action will be taken and 1 will
   * be returned.
   */
  @Implementation
  protected int update(Uri uri, ContentValues values, String where, String[] selectionArgs) {
    ContentProvider provider = getProvider(uri);
    ContentValues valuesCopy = (values == null) ? null : new ContentValues(values);
    UpdateStatement updateStatement =
        new UpdateStatement(uri, provider, valuesCopy, where, selectionArgs);
    statements.add(updateStatement);
    updateStatements.add(updateStatement);

    if (provider != null) {
      return provider.update(uri, values, where, selectionArgs);
    } else {
      return 1;
    }
  }

  @Implementation
  protected final Cursor query(
      Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    ContentProvider provider = getProvider(uri);
    if (provider != null) {
      return provider.query(uri, projection, selection, selectionArgs, sortOrder);
    } else {
      BaseCursor returnCursor = getCursor(uri);
      if (returnCursor == null) {
        return null;
      }

      returnCursor.setQuery(uri, projection, selection, selectionArgs, sortOrder);
      return returnCursor;
    }
  }

  @Implementation
  protected Cursor query(
      Uri uri,
      String[] projection,
      String selection,
      String[] selectionArgs,
      String sortOrder,
      CancellationSignal cancellationSignal) {
    ContentProvider provider = getProvider(uri);
    if (provider != null) {
      return provider.query(
          uri, projection, selection, selectionArgs, sortOrder, cancellationSignal);
    } else {
      BaseCursor returnCursor = getCursor(uri);
      if (returnCursor == null) {
        return null;
      }

      returnCursor.setQuery(uri, projection, selection, selectionArgs, sortOrder);
      return returnCursor;
    }
  }

  @Implementation
  protected String getType(Uri uri) {
    ContentProvider provider = getProvider(uri);
    if (provider != null) {
      return provider.getType(uri);
    } else {
      return null;
    }
  }

  @Implementation
  protected Bundle call(Uri uri, String method, String arg, Bundle extras) {
    ContentProvider cp = getProvider(uri);
    if (cp != null) {
      return cp.call(method, arg, extras);
    } else {
      return null;
    }
  }

  @Implementation
  protected final ContentProviderClient acquireContentProviderClient(String name) {
    ContentProvider provider = getProvider(name);
    if (provider == null) {
      return null;
    }
    return getContentProviderClient(provider, true);
  }

  @Implementation
  protected final ContentProviderClient acquireContentProviderClient(Uri uri) {
    ContentProvider provider = getProvider(uri);
    if (provider == null) {
      return null;
    }
    return getContentProviderClient(provider, true);
  }

  @Implementation
  protected final ContentProviderClient acquireUnstableContentProviderClient(String name) {
    ContentProvider provider = getProvider(name);
    if (provider == null) {
      return null;
    }
    return getContentProviderClient(provider, false);
  }

  @Implementation
  protected final ContentProviderClient acquireUnstableContentProviderClient(Uri uri) {
    ContentProvider provider = getProvider(uri);
    if (provider == null) {
      return null;
    }
    return getContentProviderClient(provider, false);
  }

  private ContentProviderClient getContentProviderClient(ContentProvider provider, boolean stable) {
    ContentProviderClient client =
        ReflectionHelpers.callConstructor(
            ContentProviderClient.class,
            ClassParameter.from(ContentResolver.class, realContentResolver),
            ClassParameter.from(IContentProvider.class, provider.getIContentProvider()),
            ClassParameter.from(boolean.class, stable));
    ShadowContentProviderClient shadowContentProviderClient = Shadow.extract(client);
    shadowContentProviderClient.setContentProvider(provider);
    return client;
  }

  @Implementation
  protected final IContentProvider acquireProvider(String name) {
    return acquireUnstableProvider(name);
  }

  @Implementation
  protected final IContentProvider acquireProvider(Uri uri) {
    return acquireUnstableProvider(uri);
  }

  @Implementation
  protected final IContentProvider acquireUnstableProvider(String name) {
    ContentProvider cp = getProvider(name);
    if (cp != null) {
      return cp.getIContentProvider();
    }
    return null;
  }

  @Implementation
  protected final IContentProvider acquireUnstableProvider(Uri uri) {
    ContentProvider cp = getProvider(uri);
    if (cp != null) {
      return cp.getIContentProvider();
    }
    return null;
  }

  /**
   * If a {@link ContentProvider} is registered for the given {@link Uri}, its {@link
   * ContentProvider#delete(Uri, String, String[])} method will be invoked.
   *
   *
Tests can verify that this method was called using {@link #getDeleteStatements()} or {@link
   * #getDeletedUris()}.
   *
   *
If no appropriate {@link ContentProvider} is found, no action will be taken and {@code 1}
   * will be returned.
   */
  @Implementation
  protected final int delete(Uri url, String where, String[] selectionArgs) {
    ContentProvider provider = getProvider(url);

    DeleteStatement deleteStatement = new DeleteStatement(url, provider, where, selectionArgs);
    statements.add(deleteStatement);
    deleteStatements.add(deleteStatement);

    if (provider != null) {
      return provider.delete(url, where, selectionArgs);
    } else {
      return 1;
    }
  }

  /**
   * If a {@link ContentProvider} is registered for the given {@link Uri}, its {@link
   * ContentProvider#bulkInsert(Uri, ContentValues[])} method will be invoked.
   *
   *
Tests can verify that this method was called using {@link #getStatements()} or {@link
   * #getInsertStatements()}.
   *
   *
If no appropriate {@link ContentProvider} is found, no action will be taken and the number
   * of rows in {@code values} will be returned.
   */
  @Implementation
  protected final int bulkInsert(Uri url, ContentValues[] values) {
    ContentProvider provider = getProvider(url);

    InsertStatement insertStatement = new InsertStatement(url, provider, values);
    statements.add(insertStatement);
    insertStatements.add(insertStatement);

    if (provider != null) {
      return provider.bulkInsert(url, values);
    } else {
      return values.length;
    }
  }

  @Implementation
  protected void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
    notifiedUris.add(new NotifiedUri(uri, observer, syncToNetwork));

    for (ContentObserverEntry entry : contentObservers) {
      if (entry.matches(uri) && entry.observer != observer) {
        entry.observer.dispatchChange(false, uri);
      }
    }
    if (observer != null && observer.deliverSelfNotifications()) {
      observer.dispatchChange(true, uri);
    }
  }

  @Implementation
  protected void notifyChange(Uri uri, ContentObserver observer) {
    notifyChange(uri, observer, false);
  }

  @Implementation
  protected ContentProviderResult[] applyBatch(
      String authority, ArrayList<ContentProviderOperation> operations)
      throws OperationApplicationException {
    ContentProvider provider = getProvider(authority);
    if (provider != null) {
      return provider.applyBatch(operations);
    } else {
      contentProviderOperations.put(authority, operations);
      return contentProviderResults;
    }
  }

  @Implementation
  protected static void requestSync(Account account, String authority, Bundle extras) {
    validateSyncExtrasBundle(extras);
    Status status = getStatus(account, authority, true);
    status.syncRequests++;
    status.syncExtras = extras;
  }

  @Implementation
  protected static void cancelSync(Account account, String authority) {
    Status status = getStatus(account, authority);
    if (status != null) {
      status.syncRequests = 0;
      if (status.syncExtras != null) {
        status.syncExtras.clear();
      }
      // This may be too much, as the above should be sufficient.
      if (status.syncs != null) {
        status.syncs.clear();
      }
    }
  }

  @Implementation
  protected static boolean isSyncActive(Account account, String authority) {
    ShadowContentResolver.Status status = getStatus(account, authority);
    // TODO: this means a sync is *perpetually* active after one request
    return status != null && status.syncRequests > 0;
  }

  @Implementation
  protected static void setIsSyncable(Account account, String authority, int syncable) {
    getStatus(account, authority, true).state = syncable;
  }

  @Implementation
  protected static int getIsSyncable(Account account, String authority) {
    return getStatus(account, authority, true).state;
  }

  @Implementation
  protected static boolean getSyncAutomatically(Account account, String authority) {
    return getStatus(account, authority, true).syncAutomatically;
  }

  @Implementation
  protected static void setSyncAutomatically(Account account, String authority, boolean sync) {
    getStatus(account, authority, true).syncAutomatically = sync;
  }

  @Implementation
  protected static void addPeriodicSync(
      Account account, String authority, Bundle extras, long pollFrequency) {
    validateSyncExtrasBundle(extras);
    removePeriodicSync(account, authority, extras);
    getStatus(account, authority, true)
        .syncs
        .add(new PeriodicSync(account, authority, extras, pollFrequency));
  }

  @Implementation
  protected static void removePeriodicSync(Account account, String authority, Bundle extras) {
    validateSyncExtrasBundle(extras);
    Status status = getStatus(account, authority);
    if (status != null) {
      for (int i = 0; i < status.syncs.size(); ++i) {
        if (isBundleEqual(extras, status.syncs.get(i).extras)) {
          status.syncs.remove(i);
          break;
        }
      }
    }
  }

  @Implementation
  protected static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) {
    return getStatus(account, authority, true).syncs;
  }

  @Implementation
  protected static void validateSyncExtrasBundle(Bundle extras) {
    for (String key : extras.keySet()) {
      Object value = extras.get(key);
      if (value == null
          || value instanceof Long
          || value instanceof Integer
          || value instanceof Boolean
          || value instanceof Float
          || value instanceof Double
          || value instanceof String
          || value instanceof Account) {
        continue;
      }

      throw new IllegalArgumentException("unexpected value type: " + value.getClass().getName());
    }
  }

  @Implementation
  protected static void setMasterSyncAutomatically(boolean sync) {
    masterSyncAutomatically = sync;
  }

  @Implementation
  protected static boolean getMasterSyncAutomatically() {
    return masterSyncAutomatically;
  }

  @Implementation(minSdk = KITKAT)
  protected void takePersistableUriPermission(@NonNull Uri uri, int modeFlags) {
    Objects.requireNonNull(uri, "uri may not be null");
    modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

    // If neither read nor write permission is specified there is nothing to do.
    if (modeFlags == 0) {
      return;
    }

    // Attempt to locate an existing record for the uri.
    for (Iterator<UriPermission> i = uriPermissions.iterator(); i.hasNext(); ) {
      UriPermission perm = i.next();
      if (uri.equals(perm.getUri())) {
        if (perm.isReadPermission()) {
          modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
        }
        if (perm.isWritePermission()) {
          modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
        }
        i.remove();
        break;
      }
    }

    addUriPermission(uri, modeFlags);
  }

  @Implementation(minSdk = KITKAT)
  protected void releasePersistableUriPermission(@NonNull Uri uri, int modeFlags) {
    Objects.requireNonNull(uri, "uri may not be null");
    modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

    // If neither read nor write permission is specified there is nothing to do.
    if (modeFlags == 0) {
      return;
    }

    // Attempt to locate an existing record for the uri.
    for (Iterator<UriPermission> i = uriPermissions.iterator(); i.hasNext(); ) {
      UriPermission perm = i.next();
      if (uri.equals(perm.getUri())) {
        // Reconstruct the current mode flags.
        int oldModeFlags =
            (perm.isReadPermission() ? Intent.FLAG_GRANT_READ_URI_PERMISSION : 0)
                | (perm.isWritePermission() ? Intent.FLAG_GRANT_WRITE_URI_PERMISSION : 0);

        // Apply the requested permission change.
        int newModeFlags = oldModeFlags & ~modeFlags;

        // Update the permission record if a change occurred.
        if (newModeFlags != oldModeFlags) {
          i.remove();
          if (newModeFlags != 0) {
            addUriPermission(uri, newModeFlags);
          }
        }
        break;
      }
    }
  }

  @Implementation(minSdk = KITKAT)
  @NonNull
  protected List<UriPermission> getPersistedUriPermissions() {
    return uriPermissions;
  }

  private void addUriPermission(@NonNull Uri uri, int modeFlags) {
    UriPermission perm = ReflectionHelpers.callConstructor(
        UriPermission.class,
        ClassParameter.from(Uri.class, uri),
        ClassParameter.from(int.class, modeFlags),
        ClassParameter.from(long.class, System.currentTimeMillis()));
    uriPermissions.add(perm);
  }

  public static ContentProvider getProvider(Uri uri) {
    if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
      return null;
    }
    return getProvider(uri.getAuthority());
  }

  private static synchronized ContentProvider getProvider(String authority) {
    if (!providers.containsKey(authority)) {
      ProviderInfo providerInfo =
          RuntimeEnvironment.application.getPackageManager().resolveContentProvider(authority, 0);
      if (providerInfo != null) {
        providers.put(providerInfo.authority, createAndInitialize(providerInfo));
      }
    }
    return providers.get(authority);
  }

  /**
   * Internal-only method, do not use!
   *
   * Instead, use
   * ```java
   * ProviderInfo info = new ProviderInfo();
   * info.authority = authority;
   * Robolectric.buildContentProvider(ContentProvider.class).create(info);
   * ```
   */
  public static synchronized void registerProviderInternal(
      String authority, ContentProvider provider) {
    providers.put(authority, provider);
  }

  public static Status getStatus(Account account, String authority) {
    return getStatus(account, authority, false);
  }

  /**
   * Retrieve information on the status of the given account.
   *
   * @param account the account
   * @param authority the authority
   * @param create whether to create if no such account is found
   * @return the account's status
   */
  public static Status getStatus(Account account, String authority, boolean create) {
    Map<Account, Status> map = syncableAccounts.get(authority);
    if (map == null) {
      map = new HashMap<>();
      syncableAccounts.put(authority, map);
    }
    Status status = map.get(account);
    if (status == null && create) {
      status = new Status();
      map.put(account, status);
    }
    return status;
  }

  public void setCursor(BaseCursor cursor) {
    this.cursor = cursor;
  }

  public void setCursor(Uri uri, BaseCursor cursorForUri) {
    this.uriCursorMap.put(uri, cursorForUri);
  }

  @SuppressWarnings({"unused", "WeakerAccess"})
  public void setNextDatabaseIdForInserts(int nextId) {
    nextDatabaseIdForInserts = nextId;
  }

  /**
   * Returns the list of {@link InsertStatement}s, {@link UpdateStatement}s, and
   * {@link DeleteStatement}s invoked on this {@link ContentResolver}.
   *
   * @return a list of statements
   */
  @SuppressWarnings({"unused", "WeakerAccess"})
  public List<Statement> getStatements() {
    return statements;
  }

  /**
   * Returns the list of {@link InsertStatement}s for corresponding calls to
   * {@link ContentResolver#insert(Uri, ContentValues)} or
   * {@link ContentResolver#bulkInsert(Uri, ContentValues[])}.
   *
   * @return a list of insert statements
   */
  @SuppressWarnings({"unused", "WeakerAccess"})
  public List<InsertStatement> getInsertStatements() {
    return insertStatements;
  }

  /**
   * Returns the list of {@link UpdateStatement}s for corresponding calls to
   * {@link ContentResolver#update(Uri, ContentValues, String, String[])}.
   *
   * @return a list of update statements
   */
  @SuppressWarnings({"unused", "WeakerAccess"})
  public List<UpdateStatement> getUpdateStatements() {
    return updateStatements;
  }

  @SuppressWarnings({"unused", "WeakerAccess"})
  public List<Uri> getDeletedUris() {
    List<Uri> uris = new ArrayList<>();
    for (DeleteStatement deleteStatement : deleteStatements) {
      uris.add(deleteStatement.getUri());
    }
    return uris;
  }

  /**
   * Returns the list of {@link DeleteStatement}s for corresponding calls to
   * {@link ContentResolver#delete(Uri, String, String[])}.
   *
   * @return a list of delete statements
   */
  @SuppressWarnings({"unused", "WeakerAccess"})
  public List<DeleteStatement> getDeleteStatements() {
    return deleteStatements;
  }

  @SuppressWarnings({"unused", "WeakerAccess"})
  public List<NotifiedUri> getNotifiedUris() {
    return notifiedUris;
  }

  public List<ContentProviderOperation> getContentProviderOperations(String authority) {
    List<ContentProviderOperation> operations = contentProviderOperations.get(authority);
    if (operations == null) {
      return new ArrayList<>();
    }
    return operations;
  }

  public void setContentProviderResult(ContentProviderResult[] contentProviderResults) {
    this.contentProviderResults = contentProviderResults;
  }

  @Implementation
  protected void registerContentObserver(
      Uri uri, boolean notifyForDescendents, ContentObserver observer) {
    if (uri == null || observer == null) {
      throw new NullPointerException();
    }
    contentObservers.add(new ContentObserverEntry(uri, notifyForDescendents, observer));
  }

  @Implementation(minSdk = JELLY_BEAN_MR1)
  protected void registerContentObserver(
      Uri uri, boolean notifyForDescendents, ContentObserver observer, int userHandle) {
    registerContentObserver(uri, notifyForDescendents, observer);
  }

  @Implementation
  protected void unregisterContentObserver(ContentObserver observer) {
    synchronized (contentObservers) {
      for (ContentObserverEntry entry : contentObservers) {
        if (entry.observer == observer) {
          contentObservers.remove(entry);
        }
      }
    }
  }

  @Implementation
  protected static SyncAdapterType[] getSyncAdapterTypes() {
    return syncAdapterTypes;
  }

  /** Sets the SyncAdapterType array which will be returned by {@link #getSyncAdapterTypes()}. */
  public static void setSyncAdapterTypes(SyncAdapterType[] syncAdapterTypes) {
    ShadowContentResolver.syncAdapterTypes = syncAdapterTypes;
  }

  /**
   * Returns the content observers registered for updates under the given URI.
   *
   * Will be empty if no observer is registered.
   *
   * @param uri Given URI
   * @return The content observers, or null
   */
  public Collection<ContentObserver> getContentObservers(Uri uri) {
    ArrayList<ContentObserver> observers = new ArrayList<>(1);
    for (ContentObserverEntry entry : contentObservers) {
      if (entry.matches(uri)) {
        observers.add(entry.observer);
      }
    }
    return observers;
  }

  private static ContentProvider createAndInitialize(ProviderInfo providerInfo) {
    try {
      ContentProvider provider =
          (ContentProvider) Class.forName(providerInfo.name).getDeclaredConstructor().newInstance();
      provider.attachInfo(RuntimeEnvironment.application, providerInfo);
      provider.onCreate();
      return provider;
    } catch (InstantiationException
        | ClassNotFoundException
        | IllegalAccessException
        | NoSuchMethodException
        | InvocationTargetException e) {
      throw new RuntimeException("Error instantiating class " + providerInfo.name);
    }
  }

  private BaseCursor getCursor(Uri uri) {
    if (uriCursorMap.get(uri) != null) {
      return uriCursorMap.get(uri);
    } else if (cursor != null) {
      return cursor;
    } else {
      return null;
    }
  }

  private static boolean isBundleEqual(Bundle bundle1, Bundle bundle2) {
    if (bundle1 == null || bundle2 == null) {
      return false;
    }
    if (bundle1.size() != bundle2.size()) {
      return false;
    }
    for (String key : bundle1.keySet()) {
      if (!bundle1.get(key).equals(bundle2.get(key))) {
        return false;
      }
    }
    return true;
  }

  /**
   * A statement used to modify content in a {@link ContentProvider}.
   */
  public static class Statement {
    private final Uri uri;
    private final ContentProvider contentProvider;

    Statement(Uri uri, ContentProvider contentProvider) {
      this.uri = uri;
      this.contentProvider = contentProvider;
    }

    public Uri getUri() {
      return uri;
    }

    @SuppressWarnings({"unused", "WeakerAccess"})
    public ContentProvider getContentProvider() {
      return contentProvider;
    }
  }

  /**
   * A statement used to insert content into a {@link ContentProvider}.
   */
  public static class InsertStatement extends Statement {
    private final ContentValues[] bulkContentValues;

    InsertStatement(Uri uri, ContentProvider contentProvider, ContentValues contentValues) {
      super(uri, contentProvider);
      this.bulkContentValues = new ContentValues[] {contentValues};
    }

    InsertStatement(Uri uri, ContentProvider contentProvider, ContentValues[] bulkContentValues) {
      super(uri, contentProvider);
      this.bulkContentValues = bulkContentValues;
    }

    @SuppressWarnings({"unused", "WeakerAccess"})
    public ContentValues getContentValues() {
      if (bulkContentValues.length != 1) {
        throw new ArrayIndexOutOfBoundsException("bulk insert, use getBulkContentValues() instead");
      }
      return bulkContentValues[0];
    }

    @SuppressWarnings({"unused", "WeakerAccess"})
    public ContentValues[] getBulkContentValues() {
      return bulkContentValues;
    }
  }

  /**
   * A statement used to update content in a {@link ContentProvider}.
   */
  public static class UpdateStatement extends Statement {
    private final ContentValues values;
    private final String where;
    private final String[] selectionArgs;

    UpdateStatement(
        Uri uri,
        ContentProvider contentProvider,
        ContentValues values,
        String where,
        String[] selectionArgs) {
      super(uri, contentProvider);
      this.values = values;
      this.where = where;
      this.selectionArgs = selectionArgs;
    }

    @SuppressWarnings({"unused", "WeakerAccess"})
    public ContentValues getContentValues() {
      return values;
    }

    @SuppressWarnings({"unused", "WeakerAccess"})
    public String getWhere() {
      return where;
    }

    @SuppressWarnings({"unused", "WeakerAccess"})
    public String[] getSelectionArgs() {
      return selectionArgs;
    }
  }

  /**
   * A statement used to delete content in a {@link ContentProvider}.
   */
  public static class DeleteStatement extends Statement {
    private final String where;
    private final String[] selectionArgs;

    DeleteStatement(
        Uri uri, ContentProvider contentProvider, String where, String[] selectionArgs) {
      super(uri, contentProvider);
      this.where = where;
      this.selectionArgs = selectionArgs;
    }

    @SuppressWarnings({"unused", "WeakerAccess"})
    public String getWhere() {
      return where;
    }

    @SuppressWarnings({"unused", "WeakerAccess"})
    public String[] getSelectionArgs() {
      return selectionArgs;
    }
  }

  private static class UnregisteredInputStream extends InputStream implements NamedStream {
    private final Uri uri;

    UnregisteredInputStream(Uri uri) {
      this.uri = uri;
    }

    @Override
    public int read() throws IOException {
      throw new UnsupportedOperationException(
          "You must use ShadowContentResolver.registerInputStream() in order to call read()");
    }

    @Override
    public int read(byte[] b) throws IOException {
      throw new UnsupportedOperationException(
          "You must use ShadowContentResolver.registerInputStream() in order to call read()");
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
      throw new UnsupportedOperationException(
          "You must use ShadowContentResolver.registerInputStream() in order to call read()");
    }

    @Override
    public String toString() {
      return "stream for " + uri;
    }
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static org.robolectric.shadow.api.Shadow.directlyOn;
import static org.robolectric.shadow.api.Shadow.extract;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;

import android.content.Context;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.Bootstrap;
import org.robolectric.android.internal.DisplayConfig;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.res.Qualifiers;
import org.robolectric.util.Consumer;
import org.robolectric.util.ReflectionHelpers.ClassParameter;

/**
 * For tests, display properties may be changed and devices may be added or removed
 * programmatically.
 */
@Implements(value = DisplayManager.class, minSdk = JELLY_BEAN_MR1)
public class ShadowDisplayManager {

  @RealObject private DisplayManager realDisplayManager;

  private Context context;

  @Implementation
  protected void __constructor__(Context context) {
    this.context = context;

    invokeConstructor(DisplayManager.class, realDisplayManager,
        ClassParameter.from(Context.class, context));
  }

  /**
   * Adds a simulated display and drain the main looper queue to ensure all the callbacks are
   * processed.
   *
   * @param qualifiersStr the {@link Qualifiers} string representing characteristics of the new
   *     display.
   * @return the new display's ID
   */
  public static int addDisplay(String qualifiersStr) {
    int id = getShadowDisplayManagerGlobal().addDisplay(createDisplayInfo(qualifiersStr, null));
    shadowMainLooper().idle();
    return id;
  }

  /** internal only */
  public static void configureDefaultDisplay(Configuration configuration, DisplayMetrics displayMetrics) {
    ShadowDisplayManagerGlobal shadowDisplayManagerGlobal = getShadowDisplayManagerGlobal();
    if (DisplayManagerGlobal.getInstance().getDisplayIds().length != 0) {
      throw new IllegalStateException("this method should only be called by Robolectric");
    }

    shadowDisplayManagerGlobal.addDisplay(createDisplayInfo(configuration, displayMetrics));
  }

  private static DisplayInfo createDisplayInfo(Configuration configuration, DisplayMetrics displayMetrics) {
    int widthPx = (int) (configuration.screenWidthDp * displayMetrics.density);
    int heightPx = (int) (configuration.screenHeightDp * displayMetrics.density);

    DisplayInfo displayInfo = new DisplayInfo();
    displayInfo.name = "Built-in screen";
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      displayInfo.uniqueId = "screen0";
    }
    displayInfo.appWidth = widthPx;
    displayInfo.appHeight = heightPx;
    fixNominalDimens(displayInfo);
    displayInfo.logicalWidth = widthPx;
    displayInfo.logicalHeight = heightPx;
    displayInfo.rotation = configuration.orientation == Configuration.ORIENTATION_PORTRAIT
        ? Surface.ROTATION_0
        : Surface.ROTATION_90;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      displayInfo.modeId = 0;
      displayInfo.defaultModeId = 0;
      displayInfo.supportedModes = new Display.Mode[] {
          new Display.Mode(0, widthPx, heightPx, 60)
      };
    }
    displayInfo.logicalDensityDpi = displayMetrics.densityDpi;
    displayInfo.physicalXDpi = displayMetrics.densityDpi;
    displayInfo.physicalYDpi = displayMetrics.densityDpi;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      displayInfo.state = Display.STATE_ON;
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      displayInfo.getAppMetrics(displayMetrics);
    }

    return displayInfo;
  }

  private static void fixNominalDimens(DisplayInfo displayInfo) {
    int smallest = Math.min(displayInfo.appWidth, displayInfo.appHeight);
    int largest = Math.max(displayInfo.appWidth, displayInfo.appHeight);

    displayInfo.smallestNominalAppWidth = smallest;
    displayInfo.smallestNominalAppHeight = smallest;
    displayInfo.largestNominalAppWidth = largest;
    displayInfo.largestNominalAppHeight = largest;
  }

  private static DisplayInfo createDisplayInfo(String qualifiersStr, DisplayInfo baseDisplayInfo) {
    Configuration configuration = new Configuration();
    DisplayMetrics displayMetrics = new DisplayMetrics();

    if (qualifiersStr.startsWith("+") && baseDisplayInfo != null) {
      configuration.orientation =
          (baseDisplayInfo.rotation == Surface.ROTATION_0
              || baseDisplayInfo.rotation == Surface.ROTATION_180)
              ? Configuration.ORIENTATION_PORTRAIT
              : Configuration.ORIENTATION_LANDSCAPE;
      configuration.screenWidthDp = baseDisplayInfo.logicalWidth * DisplayMetrics.DENSITY_DEFAULT
          / baseDisplayInfo.logicalDensityDpi;
      configuration.screenHeightDp = baseDisplayInfo.logicalHeight * DisplayMetrics.DENSITY_DEFAULT
          / baseDisplayInfo.logicalDensityDpi;
      configuration.densityDpi = baseDisplayInfo.logicalDensityDpi;
      displayMetrics.densityDpi = baseDisplayInfo.logicalDensityDpi;
      displayMetrics.density =
          baseDisplayInfo.logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
    }

    Bootstrap.applyQualifiers(qualifiersStr, RuntimeEnvironment.getApiLevel(), configuration,
        displayMetrics);

    return createDisplayInfo(configuration, displayMetrics);
  }

  /**
   * Changes properties of a simulated display. If `qualifiersStr` starts with a plus (`+`) sign,
   * the display's previous configuration is modified with the given qualifiers; otherwise defaults
   * are applied as described [here](http://robolectric.org/device-configuration/).
   *
   * <p>Idles the main looper to ensure all listeners are notified.
   *
   * @param displayId the display id to change
   * @param qualifiersStr the {@link Qualifiers} string representing characteristics of the new
   *     display
   */
  public static void changeDisplay(int displayId, String qualifiersStr) {
    DisplayInfo baseDisplayInfo = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
    DisplayInfo displayInfo = createDisplayInfo(qualifiersStr, baseDisplayInfo);
    getShadowDisplayManagerGlobal().changeDisplay(displayId, displayInfo);
    shadowMainLooper().idle();
  }

  /**
   * Changes properties of a simulated display. The original properties will be passed to the
   * `consumer`, which may modify them in place. The display will be updated with the new
   * properties.
   *
   * @param displayId the display id to change
   * @param consumer a function which modifies the display properties
   */
  static void changeDisplay(int displayId, Consumer<DisplayConfig> consumer) {
    DisplayInfo displayInfo = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
    if (displayInfo != null) {
      DisplayConfig displayConfig = new DisplayConfig(displayInfo);
      consumer.accept(displayConfig);
      displayConfig.copyTo(displayInfo);
      fixNominalDimens(displayInfo);
    }

    getShadowDisplayManagerGlobal().changeDisplay(displayId, displayInfo);
  }

  /**
   * Removes a simulated display and idles the main looper to ensure all listeners are notified.
   *
   * @param displayId the display id to remove
   */
  public static void removeDisplay(int displayId) {
    getShadowDisplayManagerGlobal().removeDisplay(displayId);
    shadowMainLooper().idle();
  }

  /**
   * Returns the current display saturation level set via {@link
   * android.hardware.display.DisplayManager#setSaturationLevel(float)}.
   */
  public float getSaturationLevel() {
    if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) {
      ShadowColorDisplayManager shadowCdm =
          extract(context.getSystemService(Context.COLOR_DISPLAY_SERVICE));
      return shadowCdm.getSaturationLevel() / 100f;
    }
    return getShadowDisplayManagerGlobal().getSaturationLevel();
  }

  /**
   * Sets the current display saturation level.
   *
   * This is a workaround for tests which cannot use the relevant hidden
   * {@link android.annotation.SystemApi},
   * {@link android.hardware.display.DisplayManager#setSaturationLevel(float)}.
   */
  public void setSaturationLevel(float level) {
    directlyOn(realDisplayManager, DisplayManager.class).setSaturationLevel(level);
  }

  private static ShadowDisplayManagerGlobal getShadowDisplayManagerGlobal() {
    if (Build.VERSION.SDK_INT < JELLY_BEAN_MR1) {
      throw new UnsupportedOperationException("multiple displays not supported in Jelly Bean");
    }

    return extract(DisplayManagerGlobal.getInstance());
  }
}


package org.robolectric.shadows;

import static org.robolectric.shadow.api.Shadow.directlyOn;

import android.content.Context;
import android.graphics.Typeface;
import android.text.InputFilter;
import android.text.TextPaint;
import android.text.TextWatcher;
import android.text.method.MovementMethod;
import android.text.method.TransformationMethod;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(TextView.class)
public class ShadowTextView extends ShadowView {
  @RealObject TextView realTextView;

  private CharSequence text = "";
  private TextView.BufferType bufferType = TextView.BufferType.NORMAL;
  private Integer textColorHexValue;
  private Integer hintColorHexValue;
  private float textSize = 14.0f;
  private boolean autoLinkPhoneNumbers;
  private int autoLinkMask;
  private CharSequence hintText;
  private CharSequence errorText;
  private int compoundDrawablePadding;
  private MovementMethod movementMethod;
  private boolean linksClickable;
  private int gravity;
  private int imeOptions = EditorInfo.IME_NULL;
  private TextView.OnEditorActionListener onEditorActionListener;
  private int textAppearanceId;
  private TransformationMethod transformationMethod;
  private int inputType;
  private int lines;
  protected int selectionStart = -1;
  protected int selectionEnd = -1;
  private Typeface typeface;
  private InputFilter[] inputFilters;
  private TextPaint textPaint = new TextPaint();

  private List<TextWatcher> watchers = new ArrayList<>();
  private List<Integer> previousKeyCodes = new ArrayList<>();
  private List<KeyEvent> previousKeyEvents = new ArrayList<>();
  private int paintFlags;
  private int compoundDrawablesWithIntrinsicBoundsLeft;
  private int compoundDrawablesWithIntrinsicBoundsTop;
  private int compoundDrawablesWithIntrinsicBoundsRight;
  private int compoundDrawablesWithIntrinsicBoundsBottom;

  @Implementation
  protected void setTextAppearance(Context context, int resid) {
    textAppearanceId = resid;
    directlyOn(realTextView, TextView.class).setTextAppearance(context, resid);
  }

  @Implementation
  protected boolean onKeyDown(int keyCode, KeyEvent event) {
    previousKeyCodes.add(keyCode);
    previousKeyEvents.add(event);
    return directlyOn(realTextView, TextView.class).onKeyDown(keyCode, event);
  }

  @Implementation
  protected boolean onKeyUp(int keyCode, KeyEvent event) {
    previousKeyCodes.add(keyCode);
    previousKeyEvents.add(event);
    return directlyOn(realTextView, TextView.class).onKeyUp(keyCode, event);
  }

  public int getPreviousKeyCode(int index) {
    return previousKeyCodes.get(index);
  }

  public KeyEvent getPreviousKeyEvent(int index) {
    return previousKeyEvents.get(index);
  }

  /**
   * Returns the text string of this {@code TextView}.
   *
   * Robolectric extension.
   */
  @Override
  public String innerText() {
    CharSequence text = realTextView.getText();
    return (text == null || realTextView.getVisibility() != View.VISIBLE) ? "" : text.toString();
  }

  public int getTextAppearanceId() {
    return textAppearanceId;
  }

  @Implementation
  protected void addTextChangedListener(TextWatcher watcher) {
    this.watchers.add(watcher);
    directlyOn(realTextView, TextView.class).addTextChangedListener(watcher);
  }

  @Implementation
  protected void removeTextChangedListener(TextWatcher watcher) {
    this.watchers.remove(watcher);
    directlyOn(realTextView, TextView.class).removeTextChangedListener(watcher);
  }

  /**
   * @return the list of currently registered watchers/listeners
   */
  public List<TextWatcher> getWatchers() {
    return watchers;
  }

  @HiddenApi @Implementation
  public Locale getTextServicesLocale() {
    return Locale.getDefault();
  }

  @Override
  protected void dumpAttributes(PrintStream out) {
    super.dumpAttributes(out);
    CharSequence text = realTextView.getText();
    if (text != null && text.length() > 0) {
      dumpAttribute(out, "text", text.toString());
    }
  }

  @Implementation
  protected int getPaintFlags() {
    return paintFlags;
  }

  @Implementation
  protected void setPaintFlags(int paintFlags) {
    this.paintFlags = paintFlags;
  }

  @Implementation
  protected void setOnEditorActionListener(TextView.OnEditorActionListener l) {
    this.onEditorActionListener = l;
    directlyOn(realTextView, TextView.class).setOnEditorActionListener(l);
  }

  public TextView.OnEditorActionListener getOnEditorActionListener() {
    return onEditorActionListener;
  }

  @Implementation
  protected void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
    this.compoundDrawablesWithIntrinsicBoundsLeft = left;
    this.compoundDrawablesWithIntrinsicBoundsTop = top;
    this.compoundDrawablesWithIntrinsicBoundsRight = right;
    this.compoundDrawablesWithIntrinsicBoundsBottom = bottom;
    directlyOn(realTextView, TextView.class).setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);
  }

  public int getCompoundDrawablesWithIntrinsicBoundsLeft() {
    return compoundDrawablesWithIntrinsicBoundsLeft;
  }

  public int getCompoundDrawablesWithIntrinsicBoundsTop() {
    return compoundDrawablesWithIntrinsicBoundsTop;
  }

  public int getCompoundDrawablesWithIntrinsicBoundsRight() {
    return compoundDrawablesWithIntrinsicBoundsRight;
  }

  public int getCompoundDrawablesWithIntrinsicBoundsBottom() {
    return compoundDrawablesWithIntrinsicBoundsBottom;
  }
}


package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;

import android.os.Debug;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;

@Implements(Debug.class)
public class ShadowDebug {

  private static boolean tracingStarted = false;
  private static String tracingFilename;

  @Implementation
  protected static void __staticInitializer__() {
    // Avoid calling Environment.getLegacyExternalStorageDirectory()
  }

  @Implementation
  protected static long getNativeHeapAllocatedSize() {
    return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
  }

  @Implementation(minSdk = M)
  protected static Map<String, String> getRuntimeStats() {
    return ImmutableMap.<String, String>builder().build();
  }

  @Implementation
  protected static void startMethodTracing() {
    internalStartTracing(fixTracePath(null));
  }

  @Implementation
  protected static void startMethodTracing(String tracePath, int bufferSize, int flags) {
    internalStartTracing(fixTracePath(tracePath));
  }

  @Implementation
  protected static void startMethodTracing(String tracePath) {
    internalStartTracing(fixTracePath(tracePath));
  }

  @Implementation
  protected static void startMethodTracing(String tracePath, int bufferSize) {
    internalStartTracing(fixTracePath(tracePath));
  }

  @Implementation(minSdk = LOLLIPOP)
  protected static void startMethodTracingSampling(String tracePath, int bufferSize, int intervalUs) {
    internalStartTracing(fixTracePath(tracePath));
  }

  @Implementation
  protected static void stopMethodTracing() {
    if (!tracingStarted) {
      throw new RuntimeException("Tracing is not started.");
    }

    try {
      Files.asCharSink(new File(tracingFilename), Charset.forName("UTF-8")).write("trace data");
    } catch (IOException e) {
      throw new RuntimeException("Writing trace file failed", e);
    }
    tracingStarted = false;
    tracingFilename = null;
  }

  private static void internalStartTracing(String tracePath) {
    if (tracingStarted) {
      throw new RuntimeException("Tracing is already started.");
    }
    tracingStarted = true;
    tracingFilename = tracePath;
  }

  @Resetter
  public static void reset() {
    tracingStarted = false;
    tracingFilename = null;
  }

  // Forked from android.os.Debug
  private static String fixTracePath(String tracePath) {
    String defaultTraceBody = "dmtrace";
    String defaultTraceExtension = ".trace";

    if (tracePath == null || tracePath.charAt(0) != '/') {
      final File dir = RuntimeEnvironment.application.getExternalFilesDir(null);
      if (tracePath == null) {
        tracePath = new File(dir, defaultTraceBody).getAbsolutePath();
      } else {
        tracePath = new File(dir, tracePath).getAbsolutePath();
      }
    }
    if (!tracePath.endsWith(defaultTraceExtension)) {
      tracePath += defaultTraceExtension;
    }
    return tracePath;
  }
}


package org.nutz.lang;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import org.nutz.lang.util.Regex;

/**
 * 一个统计代码的工具
 *
 * @author pangwu86(pangwu86@gmail.com)
 */
public class Code {

    private Code() {}

    /**
     * 代码分析结果。
     *
     * @author pangwu86(pangwu86@gmail.com)
     */
    public static class CodeAnalysisResult {
        // 代码行
        protected long normalLines;
        // 注释行
        protected long commentLines;
        // 空行
        protected long whiteLines;
        // 导入行
        protected long importLines;

        public CodeAnalysisResult() {}

        public CodeAnalysisResult(long normalLines,
                                  long commentLines,
                                  long whiteLines,
                                  long importLines) {
            this.normalLines = normalLines;
            this.commentLines = commentLines;
            this.whiteLines = whiteLines;
            this.importLines = importLines;
        }

        public long getNormalLines() {
            return normalLines;
        }

        public long getCommentLines() {
            return commentLines;
        }

        public long getWhiteLines() {
            return whiteLines;
        }

        public long getImportLines() {
            return importLines;
        }

        public long getTotalLines() {
            return normalLines + commentLines + whiteLines + importLines;
        }

        public String toString() {
            return String.format("All      : %d lines\n"
                                         + "comments : %d lines\n"
                                         + "blank    : %d lines\n"
                                         + "imports  : %d lines\n",
                                 normalLines,
                                 commentLines,
                                 whiteLines,
                                 importLines);
        }

    }

    /**
     * 代码统计结果。
     *
     * @author pangwu86(pangwu86@gmail.com)
     */
    public static class CodeStatisticsResult extends CodeAnalysisResult {

        private File src;

        private int fileCount;

        public CodeStatisticsResult(File src) {
            this.src = src;
        }

        public void addCodeAnalysisResult(CodeAnalysisResult analysisResult) {
            this.normalLines += analysisResult.getNormalLines();
            this.commentLines += analysisResult.getCommentLines();
            this.whiteLines += analysisResult.getWhiteLines();
            this.importLines += analysisResult.getImportLines();
            fileCount++;
        }

        public File getSrc() {
            return src;
        }

        public int getFileCount() {
            return fileCount;
        }
    }

    /**
     * 代码分析配置信息。
     *
     * @author pangwu86(pangwu86@gmail.com)
     */
    public static class CodeAnalysisConf {

        /** 包名行开头 */
        public String pakStart;

        /** 导入行开头 */
        public String impStart;

        /** 单行注解开头 */
        public String singleLineCommentStart;

        /** 多行注解开头 */
        public String multiLineCommentStart;

        /** 多行注解结尾 */
        public String multiLineCommentEnd;

        /** 空行 */
        public String emptyLinePattern;

    }

    /** 分析JAVA代码的配置项 */
    private static CodeAnalysisConf CODE_INFO_JAVA = new CodeAnalysisConf();

    static {
        CODE_INFO_JAVA.pakStart = "package ";
        CODE_INFO_JAVA.impStart = "import ";
        CODE_INFO_JAVA.singleLineCommentStart = "//";
        CODE_INFO_JAVA.multiLineCommentStart = "/*";
        CODE_INFO_JAVA.multiLineCommentEnd = "*/";
        CODE_INFO_JAVA.emptyLinePattern = "^[\\s&&[^\\n]]*$";
    }

    /**
     * 统计某个文件的信息。
     *
     * @param file
     *            被分析的文件
     * @param conf
     *            代码分析配置项(为空的话，则按照JAVA代码来进行分析统计)
     * @return 分析结果
     */
    public static CodeAnalysisResult countingCode(File file,
                                                  CodeAnalysisConf conf) {
        if (!Files.isFile(file)) {
            throw new RuntimeException("file is not a File, can't analysis it.");
        }
        if (null == conf) {
            conf = CODE_INFO_JAVA;
        }
        BufferedReader br;
        try {
            br = new BufferedReader(new FileReader(file));
        }
        catch (FileNotFoundException e) {
            throw Lang.wrapThrow(e);
        }
        boolean comment = false;
        long whiteLines = 0;
        long commentLines = 0;
        long normalLines = 0;
        long importLines = 0;
        String line = "";
        try {
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.startsWith(conf.multiLineCommentStart)
                    && !line.endsWith(conf.multiLineCommentEnd)) {
                    // 多行注释开始
                    commentLines++;
                    comment = true;
                } else if (true == comment) {
                    // 多行注释结束
                    commentLines++;
                    if (line.endsWith(conf.multiLineCommentEnd)) {
                        comment = false;
                    }
                } else if (Regex.match(conf.emptyLinePattern, line)) {
                    // 空白行(多行注解内的空白行不算在内)
                    whiteLines++;
                } else if (line.startsWith(conf.singleLineCommentStart)
                           || (line.startsWith(conf.multiLineCommentStart) && line.endsWith(conf.multiLineCommentEnd))) {
                    // 单行注释
                    commentLines++;
                } else if (line.startsWith(conf.pakStart)
                           || line.startsWith(conf.impStart)) {
                    // package与import
                    importLines++;
                } else {
                    // 代码行
                    normalLines++;
                }
            }
        }
        catch (IOException e) {
            throw Lang.wrapThrow(e);
        }
        if (br != null) {
            try {
                br.close();
            }
            catch (IOException e) {
                throw Lang.wrapThrow(e);
            }
        }
        // 记录并返回统计结果
        return new CodeAnalysisResult(normalLines,
                                      commentLines,
                                      whiteLines,
                                      importLines);
    }

    /**
     * 统计某个目录下，以特定后缀名结尾的源码信息。
     *
     * @param src
     *            源代码目录
     * @param suffix
     *            文件后缀（为空的话，则统计所有类型文件）
     * @param countSubFolder
     *            是否统计子文件夹(true的话，将递归统计所有子文件夹)
     * @param conf
     *            代码分析配置项(为空的话，则按照JAVA代码来进行分析统计)
     */
    public static CodeStatisticsResult countingCode(File src,
                                                    String suffix,
                                                    boolean countSubFolder,
                                                    CodeAnalysisConf conf) {
        if (!Files.isDirectory(src)) {
            throw new RuntimeException("src is not a File, can't analysis it.");
        }
        if (null == conf) {
            conf = CODE_INFO_JAVA;
        }
        CodeStatisticsResult statisticsResult = new CodeStatisticsResult(src);
        boolean useParticularType = !Strings.isBlank(suffix);
        folderAnalysis(src,
                       useParticularType,
                       suffix,
                       countSubFolder,
                       conf,
                       statisticsResult);
        return statisticsResult;
    }

    private static void folderAnalysis(File src,
                                       boolean useParticularType,
                                       String suffix,
                                       boolean countSubFolder,
                                       CodeAnalysisConf conf,
                                       CodeStatisticsResult statisticsResult) {
        for (File f : src.listFiles()) {
            if (countSubFolder && Files.isDirectory(f)) {
                folderAnalysis(f,
                               useParticularType,
                               suffix,
                               countSubFolder,
                               conf,
                               statisticsResult);
            } else {
                if (useParticularType
                    && !suffix.equalsIgnoreCase(f.getName()
                                                 .substring(f.getName()
                                                             .lastIndexOf('.') + 1))) {
                    continue;
                }
                statisticsResult.addCodeAnalysisResult(countingCode(f, conf));
            }
        }
    }

}


package org.nutz.lang;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.nutz.lang.util.CronSequenceGenerator;
import org.nutz.log.Log;
import org.nutz.log.Logs;

/**
 * 定时任务服务的友好封装
 * @author QinerG(qinerg@gmail.com)
 */
public abstract class Tasks {

    private static Log logger = Logs.get();

    private static ScheduledThreadPoolExecutor taskScheduler = new ScheduledThreadPoolExecutor(getBestPoolSize());
    private static List<Timer> timerList = new ArrayList<Timer>();

    /**
     * 通过 cron 表达式来配置任务的启动时间
     * @param task
     * @param cronExpression
     */
    public static void scheduleAtCron(final Runnable task, String cronExpression) {
        TimeSchedule timeSchedule = new TimeSchedule(task, cronExpression);
        timeSchedule.start();
    }

    /**
     * 立即启动，并以固定的频率来运行任务。后续任务的启动时间不受前次任务延时影响。
     * @param task 具体待执行的任务
     * @param periodSeconds 每次执行任务的间隔时间(单位秒)
     */
    public static ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long periodSeconds) {
        return scheduleAtFixedRate(task, 0, periodSeconds, TimeUnit.SECONDS);
    }

    /**
     * 在指定的延时之后开始以固定的频率来运行任务。后续任务的启动时间不受前次任务延时影响。
     * @param task 具体待执行的任务
     * @param initialDelay 首次执行任务的延时时间
     * @param periodSeconds 每次执行任务的间隔时间(单位秒)
     * @param unit 时间单位
     */
    public static ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long initialDelay, long periodSeconds, TimeUnit unit) {
        return taskScheduler.scheduleAtFixedRate(task, initialDelay, periodSeconds, unit);
    }

    /**
     * 在指定的时间点开始以固定的频率运行任务。后续任务的启动时间不受前次任务延时影响。
     * @param task 具体待执行的任务
     * @param startTime 首次运行的时间点,支持 "yyyy-MM-dd HH:mm:ss" 格式
     * @param period 每次执行任务的间隔时间
     * @param unit 时间单位
     */
    public static void scheduleAtFixedRate(Runnable task, String startTime, long period, TimeUnit unit) throws ParseException {
        Date dt = Times.D(startTime);
        scheduleAtFixedRate(task, dt, period, unit);
    }

    /**
     * 在指定的时间点开始以固定的频率运行任务。后续任务的启动时间不受前次任务延时影响。
     * @param task 具体待执行的任务
     * @param startTime 首次运行的时间点
     * @param period 每次执行任务的间隔时间
     * @param unit 时间单位
     */
    public static void scheduleAtFixedRate(final Runnable task, Date startTime, final long period, final TimeUnit unit) {
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                taskScheduler.scheduleAtFixedRate(task, 0, period, unit);
                timer.cancel();
                timerList.remove(timer);
            }
        }, startTime);
        timerList.add(timer);
    }

    /**
     * 立即启动，两次任务间保持固定的时间间隔
     * @param task 具体待执行的任务
     * @param periodSeconds 两次任务的间隔时间(单位秒)
     */
    public static ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long periodSeconds) {
        return scheduleWithFixedDelay(task, 0, periodSeconds, TimeUnit.SECONDS);
    }

    /**
     * 在指定的延时之后启动，两次任务间保持固定的时间间隔
     * @param task 具体待执行的任务
     * @param initialDelay 首次执行任务的延时时间
     * @param period 两次任务的间隔时间(单位秒)
     * @param unit 时间单位
     */
    public static ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long initialDelay, long period, TimeUnit unit) {
        return taskScheduler.scheduleWithFixedDelay(task, initialDelay, period, unit);
    }

    /**
     * 在指定的时间点启动，两次任务间保持固定的时间间隔
     * @param task 具体待执行的任务
     * @param startTime 首次运行的时间点,支持 "yyyy-MM-dd HH:mm:ss" 格式
     * @param period 两次任务的间隔时间
     * @param unit 时间单位
     */
    public static void scheduleWithFixedDelay(Runnable task, String startTime, long period, TimeUnit unit) throws ParseException {
        Date dt = Times.D(startTime);
        scheduleWithFixedDelay(task, dt, period, unit);
    }

    /**
     * 在指定的时间点启动，两次任务间保持固定的时间间隔
     * @param task 具体待执行的任务
     * @param startTime 首次运行的时间点
     * @param period 两次任务的间隔时间
     * @param unit 时间单位
     */
    public static void scheduleWithFixedDelay(final Runnable task, Date startTime, final long period, final TimeUnit unit) {
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                taskScheduler.scheduleWithFixedDelay(task, 0, period, unit);
                timer.cancel();
                timerList.remove(timer);
            }
        }, startTime);
        timerList.add(timer);
    }
    /**
     * 在指定的时间点启动任务只运行一次
     * @param task 具体待执行的任务
     * @param startTime 运行的时间点
     */
    public static void scheduleAtFixedTime(final Runnable task, Date startTime) {
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                taskScheduler.execute(task);
                timer.cancel();
                timerList.remove(timer);
            }
        }, startTime);
        timerList.add(timer);
    }
    /**
     * 在符合条件的时间点启动任务
     * @see scheduleAtCron
     * @param task 具体待执行的任务
     * @param expression  cron表达式
     */
    @Deprecated
    public static void scheduleAtFixedTime(final Runnable task, String cronExpression) {
    	scheduleAtCron(task, cronExpression);
    }

    /**
     * 调整线程池大小
     * @param threadPoolSize 线程池大小
     */
    public static void resizeThreadPool(int threadPoolSize) {
        taskScheduler.setCorePoolSize(threadPoolSize);
    }

    /**
     * 返回定时任务线程池，可做更高级的应用
     * @return 当前的线程池
     */
    public static ScheduledThreadPoolExecutor getTaskScheduler() {
        return taskScheduler;
    }

    /**
     * 关闭定时任务服务
     *
系统关闭时可调用此方法终止正在执行的定时任务，一旦关闭后不允许再向线程池中添加任务，否则会报RejectedExecutionException异常

     */
    public static void depose() {
    	int timerNum = timerList.size();
    	//清除Timer
    	synchronized (timerList) {
    		for (Timer t: timerList)
    			t.cancel();
    		timerList.clear();
    	}

        List<Runnable> awaitingExecution = taskScheduler.shutdownNow();
        logger.infof("Tasks stopping. Tasks awaiting execution: %d", timerNum + awaitingExecution.size());
    }

    /**
     * 重启动定时任务服务
     */
    public static void reset() {
        depose();
        taskScheduler = new ScheduledThreadPoolExecutor(getBestPoolSize());
    }

    /**
     * 根据 Java 虚拟机可用处理器数目返回最佳的线程数。

     * 最佳的线程数 = CPU可用核心数 / (1 - 阻塞系数)，其中阻塞系数这里设为0.9
     */
    private static int getBestPoolSize() {
        try {
            // JVM可用处理器的个数
            final int cores = Runtime.getRuntime().availableProcessors();
            // 最佳的线程数 = CPU可用核心数 / (1 - 阻塞系数)
            // TODO 阻塞系数是不是需要有个setter方法能让使用者自由设置呢？
            return (int)(cores / (1 - 0.9));
        }
        catch (Throwable e) {
            // 异常发生时姑且返回10个任务线程池
            return 10;
        }
    }
}

class TimeSchedule implements Runnable {
    private final Runnable task;
    private final CronSequenceGenerator cron;

    public TimeSchedule(Runnable task, String expression) {
        this.task = task;
        this.cron =  new CronSequenceGenerator(expression);
    }

    public void start(){
        Date startTime = cron.next(new Date());
        Tasks.scheduleAtFixedTime(this,startTime);
    }

    @Override
    public void run() {
        task.run();
        start();
    }
}


package org.nutz.lang.random;

import java.awt.Color;
import java.awt.Font;
import java.util.Arrays;
import java.util.Random;
import java.util.UUID;

import org.nutz.img.Colors;
import org.nutz.img.Fonts;
import org.nutz.lang.Strings;

/**
 * 对随机数操作的封装
 *
 * @author zozoh(zozohtnt@gmail.com)
 */
public abstract class R {

    static Random r = new Random();

    /**
     * 根据一个范围，生成一个随机的整数
     *
     * @param min
     *            最小值（包括）
     * @param max
     *            最大值（包括）
     * @return 随机数
     */
    public static int random(int min, int max) {
        return r.nextInt(max - min + 1) + min;
    }

    /**
     * 根据一个长度范围，生成一个随机的字符串，字符串内容为 [0-9a-zA-Z_]
     *
     * @param min
     *            最小值（包括）
     * @param max
     *            最大值（包括）
     * @return 随机字符串
     */

    public static StringGenerator sg(int min, int max) {
        return new StringGenerator(min, max);
    }

    /**
     * 生成一个确定长度的随机字符串，字符串内容为 [0-9a-zA-Z_]
     *
     * @param len
     *            字符串长度
     * @return 随机字符串
     */
    public static StringGenerator sg(int len) {
        return new StringGenerator(len, len);
    }

    private static final char[] _UU64 = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".toCharArray();
    private static final char[] _UU32 = "0123456789abcdefghijklmnopqrstuv".toCharArray();

    /**
     * @return 64进制表示的紧凑格式的 UUID
     * @see org.nutz.lang.random.R#UU64(UUID)
     */
    public static String UU64() {
        return UU64(UUID.randomUUID());
    }

    /**
     * 返回一个 UUID ，并用 64 进制转换成紧凑形式的字符串，内容为 [\\-0-9a-zA-Z_]
     *

     * 比如一个类似下面的 UUID:
     *
     *

     * a6c5c51c-689c-4525-9bcd-c14c1e107c80
     * 一共 128 位，分做L64 和 R64，分为为两个 64位数（两个 long）
     *    > L = uu.getLeastSignificantBits();
     *    > R = uu.getMostSignificantBits();
     * 而一个 64 进制数，是 6 位，因此我们取值的顺序是
     * 1. 从L64位取10次，每次取6位
     * 2. 从L64位取最后的4位 ＋ R64位头2位拼上
     * 3. 从R64位取10次，每次取6位
     * 4. 剩下的两位最后取
     * 这样，就能用一个 22 长度的字符串表示一个 32 长度的UUID，压缩了 1/3
     *

     *
     * @param uu
     *            UUID 对象
     * @return 64进制表示的紧凑格式的 UUID
     */
    public static String UU64(UUID uu) {
        int index = 0;
        char[] cs = new char[22];
        long L = uu.getMostSignificantBits();
        long R = uu.getLeastSignificantBits();
        long mask = 63;
        // 从L64位取10次，每次取6位
        for (int off = 58; off >= 4; off -= 6) {
            long hex = (L & (mask << off)) >>> off;
            cs[index++] = _UU64[(int) hex];
        }
        // 从L64位取最后的4位 ＋ R64位头2位拼上
        int l = (int) (((L & 0xF) << 2) | ((R & (3 << 62)) >>> 62));
        cs[index++] = _UU64[l];
        // 从R64位取10次，每次取6位
        for (int off = 56; off >= 2; off -= 6) {
            long hex = (R & (mask << off)) >>> off;
            cs[index++] = _UU64[(int) hex];
        }
        // 剩下的两位最后取
        cs[index++] = _UU64[(int) (R & 3)];
        // 返回字符串
        return new String(cs);
    }

    /**
     * 从一个 UU64 恢复回一个 UUID 对象
     *
     * @param uu64
     *            64进制表示的 UUID, 内容为 [\\-0-9a-zA-Z_]
     * @return UUID 对象
     */
    public static UUID fromUU64(String uu64) {
        String uu16 = UU16FromUU64(uu64);
        return UUID.fromString(UU(uu16));
    }

    public static String UU32(UUID uu) {
        StringBuilder sb = new StringBuilder();
        long m = uu.getMostSignificantBits();
        long l = uu.getLeastSignificantBits();
        for (int i = 0; i < 13; i++) {
            sb.append(_UU32[(int) (m >> ((13 - i - 1) * 5)) & 31]);
        }
        for (int i = 0; i < 13; i++) {
            sb.append(_UU32[(int) (l >> ((13 - i - 1)) * 5) & 31]);
        }
        return sb.toString();
    }

    public static String UU32() {
        return UU32(UUID.randomUUID());
    }

    public static UUID fromUU32(String u32) {
        return new UUID(parseUnsignedLong(u32.substring(0, 13), 32),
                        parseUnsignedLong(u32.substring(13), 32));
    }

    public static long parseUnsignedLong(String s, int radix) {
        int len = s.length();
        long first = Long.parseLong(s.substring(0, len - 1), radix);
        int second = Character.digit(s.charAt(len - 1), radix);
        return first * radix + second;
    }

    /**
     * 将紧凑格式的 UU16 字符串变成标准 UUID 格式的字符串
     *
     * @param uu16
     * @return 标准 UUID 字符串
     */
    public static String UU(String uu16) {
        StringBuilder sb = new StringBuilder();
        sb.append(uu16.substring(0, 8));
        sb.append('-');
        sb.append(uu16.substring(8, 12));
        sb.append('-');
        sb.append(uu16.substring(12, 16));
        sb.append('-');
        sb.append(uu16.substring(16, 20));
        sb.append('-');
        sb.append(uu16.substring(20));
        return sb.toString();
    }

    private static final char[] _UU16 = "0123456789abcdef".toCharArray();

    /**
     * 将一个 UU64 表示的紧凑字符串，变成 UU16 表示的字符串
     *
     *

     * 每次取2个字符，恢复成3个byte，重复10次， 最后一次，是用最后2个字符，恢复回2个byte </prev>
     *
     * @param uu64
     *            uu64 64进制表示的 UUID, 内容为 [\\-0-9a-zA-Z_]
     * @return 16进制表示的紧凑格式的 UUID
     */
    public static String UU16FromUU64(String uu64) {
        byte[] bytes = new byte[32];
        char[] cs = uu64.toCharArray();
        int index = 0;
        // 每次取2个字符，恢复成3个byte，重复10次，
        for (int i = 0; i < 10; i++) {
            int off = i * 2;
            char cl = cs[off];
            char cr = cs[off + 1];
            int l = Arrays.binarySearch(_UU64, cl);
            int r = Arrays.binarySearch(_UU64, cr);
            int n = (l << 6) | r;
            bytes[index++] = (byte) ((n & 0xF00) >>> 8);
            bytes[index++] = (byte) ((n & 0xF0) >>> 4);
            bytes[index++] = (byte) (n & 0xF);
        }
        // 最后一次，是用最后2个字符，恢复回2个byte
        char cl = cs[20];
        char cr = cs[21];
        int l = Arrays.binarySearch(_UU64, cl);
        int r = Arrays.binarySearch(_UU64, cr);
        int n = (l << 2) | r;
        bytes[index++] = (byte) ((n & 0xF0) >>> 4);
        bytes[index++] = (byte) (n & 0xF);

        // 返回 UUID 对象
        char[] names = new char[32];
        for (int i = 0; i < bytes.length; i++)
            names[i] = _UU16[bytes[i]];
        return new String(names);
    }

    /**
     * @return 16进制表示的紧凑格式的 UUID
     */
    public static String UU16() {
        return UU16(UUID.randomUUID());
    }

    /**
     * @param uu
     *            UUID 对象
     * @return 16进制表示的紧凑格式的 UUID
     */
    public static String UU16(UUID uu) {
        return Strings.alignRight(Long.toHexString(uu.getMostSignificantBits()), 16, '0')
               + Strings.alignRight(Long.toHexString(uu.getLeastSignificantBits()), 16, '0');
    }

    /**
     * 返回指定长度由随机数字+小写字母组成的字符串
     *
     * @param length
     *            指定长度
     * @return 随机字符串
     */
    public static String captchaChar(int length) {
        return captchaChar(length, false);
    }

    /**
     * 返回指定长度随机数字+字母(大小写敏感)组成的字符串
     *
     * @param length
     *            指定长度
     * @param caseSensitivity
     *            是否区分大小写
     * @return 随机字符串
     */
    public static String captchaChar(int length, boolean caseSensitivity) {
        StringBuilder sb = new StringBuilder();
        Random rand = new Random();// 随机用以下三个随机生成器
        Random randdata = new Random();
        int data = 0;
        for (int i = 0; i < length; i++) {
            int index = rand.nextInt(caseSensitivity ? 3 : 2);
            // 目的是随机选择生成数字，大小写字母
            switch (index) {
            case 0:
                data = randdata.nextInt(10);// 仅仅会生成0~9, 0~9的ASCII为48~57
                sb.append(data);
                break;
            case 1:
                data = randdata.nextInt(26) + 97;// 保证只会产生ASCII为97~122(a-z)之间的整数,
                sb.append((char) data);
                break;
            case 2: // caseSensitivity为true的时候, 才会有大写字母
                data = randdata.nextInt(26) + 65;// 保证只会产生ASCII为65~90(A~Z)之间的整数
                sb.append((char) data);
                break;
            }
        }
        return sb.toString();
    }

    /**
     * 返回指定长度随机数字组成的字符串
     *
     * @param length
     *            指定长度
     * @return 随机字符串
     */
    public static String captchaNumber(int length) {
        StringBuilder sb = new StringBuilder();
        Random rand = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(rand.nextInt(10));
        }
        return sb.toString();
    }

    /**
     * 设置随机数生成器的实例, 例如 SecureRandom
     *
     * @param r
     *            随机生成器,不可以是null
     */
    public static void setR(Random r) {
        if (r == null)
            throw new NullPointerException("Random MUST NOT NULL");
        R.r = r;
    }

    /**
     * 获取一个随机颜色
     *
     * @return 颜色
     */
    public static Color color() {
        return Colors.randomColor();
    }

    /**
     * 获取一个随机颜色，格式为“rgb(12, 25, 33)”
     *
     * @return rgb格式字符串
     */
    public static String colorRGB() {
        return Colors.toRGB(Colors.randomColor());
    }

    /**
     * 获取一个随机字体，不能保证非英文字符产生乱码问题
     *
     * @param style
     *            字体样式 支持：Font.PLAIN Font.BOLD Font.ITALIC
     * @param size
     *            字体大小
     * @return 字体
     */
    public static Font font(int style, int size) {
        return Fonts.random(style, size);
    }

    public static Random get() {
        return r;
    }
}


package org.nutz.dao;

import java.lang.reflect.Array;
import java.util.Collection;

import org.nutz.dao.entity.Entity;
import org.nutz.dao.entity.MappingField;
import org.nutz.dao.impl.SimpleNesting;
import org.nutz.dao.jdbc.ValueAdaptor;
import org.nutz.dao.pager.Pager;
import org.nutz.dao.sql.Criteria;
import org.nutz.dao.sql.GroupBy;
import org.nutz.dao.sql.OrderBy;
import org.nutz.dao.sql.Pojo;
import org.nutz.dao.util.Daos;
import org.nutz.dao.util.cnd.SimpleCondition;
import org.nutz.dao.util.cri.Exps;
import org.nutz.dao.util.cri.NestExps;
import org.nutz.dao.util.cri.SimpleCriteria;
import org.nutz.dao.util.cri.SqlExpression;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.segment.CharSegment;
import org.nutz.lang.util.Callback2;

/**
 * 是 Condition 的一个实现，这个类给你比较方便的方法来构建 Condition 接口的实例。
 *
 *
在 Dao 接口中使用

 *
 * 比如一个通常的查询:
 *

 * List<Pet> pets = dao.query(Pet.class,
 * Cnd.where("name","LIKE","B%").asc("name"), null);
 *
 *
链式赋值示例

 * Cnd.where("id", ">", 34).and("name","LIKE","T%").asc("name");

 * 相当于

 * WHERE id>34 AND name LIKE 'T%' ORDER BY name ASC
 *

 * Cnd.orderBy().desc("id");

 * 相当于

 * ORDER BY id DESC
 *
 *
 带括号的条件语句 where (name="wendal" or age<18) and location != "地球"

 * Cnd.where(Cnd.exps("name", "=", "wendal").or("age", "<", 18)).and("location", "!=", "地球")
 *
 *
静态条件,直接拼入sql,不做任何转义. Oracle的日期传Date对象,而非用to_date等数据库方法

 * Cnd.where(new Static("ct < to_date('2015-06-26')")).and(...........)
 *

 *
 *
between用法

 * Cnd.where("age", "between", new Object[]{19,29}).and(...........)
 *

 *
 *
你还需要知道的是:

 *

 *
你设置的字段名，是 java 的字段名 -- 如果 Entity 里有，那么会被转换成数据库字段名
 *
如果你设置的是 entity 中不存在的 java 字段名，则被认为是数据库字段名，将直接使用
 *
你的值，如果是字符串，或者其他类字符串对象（某种 CharSequence），那么在转换成 SQL 时，会正确被单引号包裹
 *
你的值如果是不可理解的自定义对象，会被转化成字符串处理
 *

 *
 * @author zozoh(zozohtnt@gmail.com)
 * @author 蛋蛋 [TopCoderMyDream@gmail.com]
 * @see org.nutz.dao.Condition
 */
public class Cnd implements OrderBy, Criteria, GroupBy {

    private static final long serialVersionUID = 1L;

    /**
     * 用字符串和参数格式化出一个条件语句,注意,不会抹除特殊字符
     * @param format sql条件
     * @param args 参数
     * @return 条件对象
     */
    public static Condition format(String format, Object... args) {
        return Strings.isBlank(format) ? null : new SimpleCondition(format,
                args);
    }

    /***
     * 直接用字符串生成一个条件对象
     * @param str sql条件
     * @return 条件对象
     */
    public static Condition wrap(String str) {
        return Strings.isBlank(str) ? null : new SimpleCondition((Object) str);
    }

    /**
     * 使用CharSegment拼装一个条件对象
     * @param sql sql模板
     * @param value 参数
     * @return 条件对象
     * @see org.nutz.lang.segment.CharSegment
     */
    public static Condition wrap(String sql, Object value) {
        return Strings.isBlank(sql) ? null
                : new SimpleCondition(new CharSegment(sql).setBy(value));
    }

    /**
     * 生成一个条件表达式
     * @param name Java属性或字段名称
     * @param op   操作符,可以是 = like 等等
     * @param value 参数值.
     * @return 条件表达式
     */
    public static SqlExpression exp(String name, String op, Object value) {
        if(value!=null && value instanceof Nesting){
            return NestExps.create(name, op, (Nesting) value);
        }
        return Exps.create(name, op, value);
    }

    /**
     * 生成一个条件表达式组
     * @param name Java属性或字段名称
     * @param op   操作符,可以是 = like 等等
     * @param value 参数值.
     * @return 条件表达式组
     */
    public static SqlExpressionGroup exps(String name, String op, Object value) {
        return exps(exp(name, op, value));
    }

    /**
     * 将一个条件表达式封装为条件表达式组
     * @param exp 原本的条件表达式
     * @return 条件表达式组
     */
    public static SqlExpressionGroup exps(SqlExpression exp) {
        return new SqlExpressionGroup().and(exp);
    }

    /**
     * 生成一个新的Cnd实例
     * @param name java属性或字段名称, 推荐用Java属性
     * @param op 操作符,可以是= like等等
     * @param value 参数值. 如果操作符是between,参数值需要是new Object[]{12,39}形式
     * @return Cnd实例
     */
    public static Cnd where(String name, String op, Object value) {
        return new Cnd(Cnd.exp(name, op, value));
    }

    /**
     * 用一个条件表达式构建一个Cnd实例
     * @param e 条件表达式
     * @return Cnd实例
     */
    public static Cnd where(SqlExpression e) {
        return new Cnd(e);
    }

    /**
     * 生成一个简单条件对象
     */
    public static SimpleCriteria cri() {
        return new SimpleCriteria();
    }

    /**
     * 单纯生成一个Orderby条件
     * @return OrderBy实例
     */
    public static OrderBy orderBy() {
        return new Cnd();
    }

    /**
     * @return 一个 Cnd 的实例
     * @deprecated Since 1.b.50 不推荐使用这个函数构建 Cnd 的实例，因为看起来语意不明的样子
     */
    public static Cnd limit() {
        return new Cnd();
    }

    /**
     * @return 一个 Cnd 的实例
     */
    public static Cnd NEW() {
        return new Cnd();
    }

    /**
     * 用SimpleCriteria生成一个Cnd实例
     * @param cri SimpleCriteria实例
     * @return Cnd实例
     */
    public static Cnd byCri(SimpleCriteria cri) {
        return new Cnd().setCri(cri);
    }

    /*------------------------------------------------------------------*/

    protected SimpleCriteria cri;

    protected Cnd() {
        cri = new SimpleCriteria();
    }

    private Cnd setCri(SimpleCriteria cri) {
        this.cri = cri;
        return this;
    }

    /**
     * 获取内部的where属性
     * @return SimpleCriteria实例
     */
    public SimpleCriteria getCri() {
        return cri;
    }

    protected Cnd(SqlExpression exp) {
        this();
        cri.where().and(exp);
    }

    /**
     * 按Java属性/字段属性进行升序. 不进行SQL特殊字符抹除  cnd.asc("age")
     * @param name Java属性/字段属性
     */
    public OrderBy asc(String name) {
        cri.asc(name);
        return this;
    }

    /**
     * 按Java属性/字段属性进行降序. 不进行SQL特殊字符抹除 cnd.desc("age")
     * @param name Java属性/字段属性
     */
    public OrderBy desc(String name) {
        cri.desc(name);
        return this;
    }

    /**
     * 当dir为asc时判断为升序,否则判定为降序. cnd.orderBy("age", "asc")
     * @param name Java属性/字段属性
     * @param dir asc或其他
     * @return OrderBy实例,事实上就是当前对象
     */
    public OrderBy orderBy(String name, String dir) {
        if ("asc".equalsIgnoreCase(dir)) {
            this.asc(name);
        } else {
            this.desc(name);
        }
        return this;
    }

    /**
     * Cnd.where(...).and(Cnd.exp(.........)) 或 Cnd.where(...).and(Cnd.exps(.........))
     * @param exp 条件表达式
     * @return 当前对象,用于链式调用
     */
    public Cnd and(SqlExpression exp) {
        cri.where().and(exp);
        return this;
    }

    /**
     * Cnd.where(...).and("age", "<", 40)
     * @param name Java属性或字段名称,推荐用Java属性,如果有的话
     * @param op 操作符,可以是 = like等
     * @param value 参数值, 如果是between的话需要传入new Object[]{19,28}
     * @return 当前对象,用于链式调用
     */
    public Cnd and(String name, String op, Object value) {
        return and(Cnd.exp(name, op, value));
    }

    /**
     * Cnd.where(...).or(Cnd.exp(.........)) 或 Cnd.where(...).or(Cnd.exps(.........))
     * @param exp 条件表达式
     * @return 当前对象,用于链式调用
     */
    public Cnd or(SqlExpression exp) {
        cri.where().or(exp);
        return this;
    }

    /**
     * Cnd.where(...).or("age", "<", 40)
     * @param name Java属性或字段名称,推荐用Java属性,如果有的话
     * @param op 操作符,可以是 = like等
     * @param value 参数值, 如果是between的话需要传入new Object[]{19,28}
     * @return 当前对象,用于链式调用
     */
    public Cnd or(String name, String op, Object value) {
        return or(Cnd.exp(name, op, value));
    }

    /**
     * and一个条件表达式并且取非
     * @param exp 条件表达式
     * @return 当前对象,用于链式调用
     */
    public Cnd andNot(SqlExpression exp) {
        cri.where().and(exp.setNot(true));
        return this;
    }

    /**
     * and一个条件,并且取非
     * @param name Java属性或字段名称,推荐用Java属性,如果有的话
     * @param op 操作符,可以是 = like等
     * @param value 参数值, 如果是between的话需要传入new Object[]{19,28}
     * @return 当前对象,用于链式调用
     */
    public Cnd andNot(String name, String op, Object value) {
        return andNot(Cnd.exp(name, op, value));
    }

    /**
     * @see Cnd#andNot(SqlExpression)
     */
    public Cnd orNot(SqlExpression exp) {
        cri.where().or(exp.setNot(true));
        return this;
    }

    /**
     * @see Cnd#andNot(String, String, Object)
     */
    public Cnd orNot(String name, String op, Object value) {
        return orNot(Cnd.exp(name, op, value));
    }

    /**
     * 获取分页对象,默认是null
     */
    public Pager getPager() {
        return cri.getPager();
    }

    /**
     * 根据实体Entity将本对象转化为sql语句, 条件表达式中的name属性将转化为数据库字段名称
     */
    public String toSql(Entity<?> en) {
        return cri.toSql(en);
    }

    /**
     * 判断两个Cnd是否相等
     */
    public boolean equals(Object obj) {
        return cri.equals(obj);
    }

    /**
     * 直接转为SQL语句, 如果setPojo未曾调用, 条件表达式中的name属性未映射为数据库字段
     */
    public String toString() {
        return cri.toString();
    }

    /**
     * 关联的Pojo,可以用于toString时的name属性映射
     */
    public void setPojo(Pojo pojo) {
        cri.setPojo(pojo);
    }

    /**
     * 获取已设置的Pojo, 默认为null
     */
    public Pojo getPojo() {
        return cri.getPojo();
    }

    public void joinSql(Entity<?> en, StringBuilder sb) {
        cri.joinSql(en, sb);
    }

    public int joinAdaptor(Entity<?> en, ValueAdaptor[] adaptors, int off) {
        return cri.joinAdaptor(en, adaptors, off);
    }

    public int joinParams(Entity<?> en, Object obj, Object[] params, int off) {
        return cri.joinParams(en, obj, params, off);
    }

    public int paramCount(Entity<?> en) {
        return cri.paramCount(en);
    }

    /**
     * 获取Cnd中的where部分,注意,对SqlExpressionGroup的修改也会反映到Cnd中,因为是同一个对象
     */
    public SqlExpressionGroup where() {
        return cri.where();
    }

    /**
     * 分组
     * @param names java属性或数据库字段名称
     */
    public GroupBy groupBy(String... names) {
        cri.groupBy(names);
        return this;
    }

    /**
     * 分组中的having条件
     * @param cnd 条件语句
     */
    public GroupBy having(Condition cnd) {
        cri.having(cnd);
        return this;
    }

    /**
     * 单独获取排序条件,建议使用asc或desc,而非直接取出排序条件. 取出的对象仅包含分组条件, 不包含where等部分
     */
    public OrderBy getOrderBy() {
        return cri.getOrderBy();
    }

    /**
     * 分页
     * @param pageNumber 页数, 若小于1则代表全部记录
     * @param pageSize 每页数量
     * @return 当前对象,用于链式调用
     */
    public Cnd limit(int pageNumber, int pageSize) {
        cri.setPager(pageNumber, pageSize);
        return this;
    }

    /**
     * 设置每页大小,并设置页数为1
     * @param pageSize 每页大小
     * @return 当前对象,用于链式调用
     */
    @Deprecated
    public Cnd limit(int pageSize) {
        cri.setPager(1, pageSize);
        return this;
    }

    /**
     * 直接设置分页对象, 可以new Pager或dao.createPager得到
     * @param pager 分页对象
     * @return 当前对象,用于链式调用
     */
    public Cnd limit(Pager pager) {
        cri.setPager(pager);
        return this;
    }

    protected static FieldMatcher dftFromFieldMatcher = new FieldMatcher().setIgnoreNull(true).setIgnoreZero(true);

    /**
     * 用默认规则(忽略零值和空值)生成Cnd实例
     * @param dao Dao实例,不能为null
     * @param obj 对象, 若为null,则返回值为null, 不可以是Class/字符串/数值/布尔类型
     * @return Cnd实例
     */
    public static Cnd from(Dao dao, Object obj) {
        return from(dao, obj, dftFromFieldMatcher);
    }

    /**
     * 根据一个对象生成Cnd条件, FieldMatcher详细控制.

     * assertEquals(" WHERE name='wendal' AND age=0", Cnd.from(dao, pet, FieldMatcher.make("age|name", null, true).setIgnoreDate(true)).toString());
     * @param dao Dao实例
     * @param obj 基对象,不可以是Class,字符串,数值和Boolean
     * @param matcher 过滤字段属性, 可配置哪些字段可用/不可用/是否忽略空值/是否忽略0值/是否忽略java.util.Date类及其子类的对象/是否忽略@Id所标注的主键属性/是否忽略 \@Name 所标注的主键属性/是否忽略 \@Pk 所引用的复合主键
     * @return Cnd条件
     */
    public static Cnd from(Dao dao, Object obj, FieldMatcher matcher) {
        final SqlExpressionGroup exps = new SqlExpressionGroup();
        boolean re = Daos.filterFields(obj, matcher, dao, new Callback2<MappingField, Object>() {
            public void invoke(MappingField mf, Object val) {
                exps.and(mf.getName(), "=", val);
            }
        });
        if (re)
            return Cnd.where(exps);
        return null;
    }

    /**
     * 若value为null/空白字符串/空集合/空数组,则本条件不添加.
     * @see Cnd#and(String, String, Object)
     */
    public Cnd andEX(String name, String op, Object value) {
        return and(Cnd.expEX(name, op, value));
    }

    /**
     * 若value为null/空白字符串/空集合/空数组,则本条件不添加.
     * @see Cnd#or(String, String, Object)
     */
    public Cnd orEX(String name, String op, Object value) {
        return or(Cnd.expEX(name, op, value));
    }

    public static SqlExpression expEX(String name, String op, Object value) {
        if (_ex(value))
            return null;
        return Cnd.exp(name, op, value);
    }

    @SuppressWarnings("rawtypes")
    public static boolean _ex(Object value) {
        return value == null
                || (value instanceof CharSequence && Strings.isBlank((CharSequence)value))
                || (value instanceof Collection && ((Collection)value).isEmpty())
                || (value.getClass().isArray() && Array.getLength(value) == 0);
    }

    public GroupBy getGroupBy() {
        return cri.getGroupBy();
    }

    /**
     * 构造一个可嵌套条件，需要dao支持才能映射类与表和属性与列
     */
    public static Nesting nst(Dao dao){
        return new SimpleNesting(dao);
    }

    /**
     * 克隆当前Cnd实例
     * @return 一模一样的兄弟
     */
    public Cnd clone() {
        return Lang.fromBytes(Lang.toBytes(this),Cnd.class);
    }

    /**
     * 仅拷贝where条件, 不拷贝排序/分组/分页
     */
    public Cnd cloneWhere() {
        return Cnd.where(this.cri.where().clone());
    }
}


package org.nutz.img;

import static java.lang.Integer.parseInt;
import static org.nutz.lang.Strings.dup;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.nutz.lang.Strings;
import org.nutz.lang.random.R;

/**
 * 提供快捷的解析颜色值的方法
 *
 * 颜色值的字符串类型支持如下:
 *

 *
RGB: #FFF
 *
RRGGBB: #F0F0F0
 *
ARGB: #9FE5
 *
AARRGGBB: #88FF8899
 *
RGB值: rgb(255,33,89)
 *
RGBA值: rgba(6,6,6,0.8)
 *

 *
 * @author zozoh(zozohtnt@gmail.com)
 */
public final class Colors {

    /**
     * @see #as(String)
     *
     * @deprecated
     */
    @Deprecated
    public static Color fromString(String str) {
        return as(str);
    }

    /**
     * 将字符串变成颜色值
     *
     * @param str
     *            颜色字符串，详细，请参看本类的总体描述，如果为空，则表示黑色
     * @return 颜色对象
     */
    public static Color as(String str) {
        if (null == str)
            return Color.BLACK;

        // 整理一下字符串以便后面匹配分析
        str = Strings.trim(str.toUpperCase());

        if (str.startsWith("#")) {
            str = str.substring(1);
        }

        if (str.endsWith(";"))
            str = str.substring(0, str.length() - 1);

        // RGB: #FFF
        Pattern p = Pattern.compile("^([0-9A-F])([0-9A-F])([0-9A-F])$");
        Matcher m = p.matcher(str);
        if (m.find()) {
            return new Color(parseInt(dup(m.group(1), 2), 16),
                             parseInt(dup(m.group(2), 2), 16),
                             parseInt(dup(m.group(3), 2), 16));
        }

        // RRGGBB: #F0F0F0
        p = Pattern.compile("^([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$");
        m = p.matcher(str);
        if (m.find()) {
            return new Color(parseInt(m.group(1), 16),
                             parseInt(m.group(2), 16),
                             parseInt(m.group(3), 16));
        }

        // ARGB: #9FE5
        p = Pattern.compile("^([0-9A-F])([0-9A-F])([0-9A-F])([0-9A-F])$");
        m = p.matcher(str);
        if (m.find()) {
            return new Color(parseInt(dup(m.group(2), 2), 16),
                             parseInt(dup(m.group(3), 2), 16),
                             parseInt(dup(m.group(4), 2), 16),
                             parseInt(dup(m.group(1), 2), 16));
        }

        // AARRGGBB: #88FF8899
        p = Pattern.compile("^([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$");
        m = p.matcher(str);
        if (m.find()) {
            return new Color(parseInt(m.group(2), 16),
                             parseInt(m.group(3), 16),
                             parseInt(m.group(4), 16),
                             parseInt(m.group(1), 16));
        }

        // RGB值: rgb(255,33,89)
        p = Pattern.compile("^RGB\\s*[(]\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*[)]$");
        m = p.matcher(str);
        if (m.find()) {
            return new Color(parseInt(m.group(1), 10),
                             parseInt(m.group(2), 10),
                             parseInt(m.group(3), 10));
        }

        // // RGBA值: rgba(6,6,6,255)
        // p =
        // Pattern.compile("^RGBA\\s*[(]\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*[)]$");
        // m = p.matcher(str);
        // if (m.find()) {
        // return new Color(parseInt(m.group(1), 10),
        // parseInt(m.group(2), 10),
        // parseInt(m.group(3), 10),
        // parseInt(m.group(4), 10));
        // }

        // RGBA值: rgba(6,6,6,0.9)
        p = Pattern.compile("^RGBA\\s*[(]\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*((\\d[.])?\\d+)\\s*[)]$");
        m = p.matcher(str);
        if (m.find()) {
            float alpha = Float.parseFloat(m.group(4));

            return new Color(parseInt(m.group(1), 10),
                             parseInt(m.group(2), 10),
                             parseInt(m.group(3), 10),
                             (int) (255.0f * alpha));
        }

        // 全都匹配不上，返回黑色
        return Color.BLACK;
    }

    /**
     * Color转换为 “rgb(12, 25, 33)” 格式字符串
     *
     * @param color
     *            颜色
     * @return 文字格式
     */
    public static String toRGB(Color color) {
        return String.format("rgb(%d, %d, %d)", color.getRed(), color.getGreen(), color.getBlue());
    }

    /**
     * 获取一个制定范围内的颜色
     *
     * @param min
     *            最小取值，范围0-255
     * @param max
     *            最大取值，范围0-255
     * @return 颜色
     */
    public static Color randomColor(int min, int max) {
        if (min > 255) {
            min = 255;
        }
        if (min < 0) {
            min = 0;
        }
        if (max > 255) {
            max = 255;
        }
        if (max < 0) {
            max = 0;
        }
        return new Color(min + R.random(0, max - min),
                         min + R.random(0, max - min),
                         min + R.random(0, max - min));
    }

    /**
     * 获取一个随机颜色
     *
     * @return 颜色
     */
    public static Color randomColor() {
        return randomColor(0, 255);
    }

    /**
     * 获取图片指定像素点的RGB值
     *
     * @param srcIm
     *            源图片
     * @param x
     *            横坐标
     * @param y
     *            纵坐标
     * @return RGB值数组
     */
    public static int[] getRGB(BufferedImage srcIm, int x, int y) {
        int pixel = srcIm.getRGB(x, y);
        return getRGB(pixel);
    }

    /**
     * 获取像素点的RGB值(三元素数组)）
     *
     * @param pixel
     *            像素RGB值
     * @return RGB值数组
     */
    public static int[] getRGB(int pixel) {
        int r = (pixel >> 16) & 0xff;
        int g = (pixel >> 8) & 0xff;
        int b = pixel & 0xff;
        return new int[]{r, g, b};
    }

    public static int getAlpha(int pixel) {
        ColorModel cm = ColorModel.getRGBdefault();
        return cm.getAlpha(pixel);
    }

    /**
     * 获取图片指定像素点的亮度值(YUV中的Y)
     *
     * @param srcIm
     *            源图片
     * @param x
     *            横坐标
     * @param y
     *            纵坐标
     * @return 亮度值
     */
    public static int getLuminance(BufferedImage srcIm, int x, int y) {
        int[] rgb = getRGB(srcIm, x, y);
        return (int) (0.3 * rgb[0] + 0.59 * rgb[1] + 0.11 * rgb[2]); // 加权法
    }

    /**
     * 获取图片指定像素点的亮度值(YUV中的Y) 类型为double
     *
     * @param srcIm
     *            源图片
     * @param x
     *            横坐标
     * @param y
     *            纵坐标
     * @return 亮度值
     */
    public static double getLuminanceDouble(BufferedImage srcIm, int x, int y) {
        int[] rgb = getRGB(srcIm, x, y);
        return 0.3 * rgb[0] + 0.59 * rgb[1] + 0.11 * rgb[2]; // 加权法
    }

    /**
     * 获取图片指定像素点的灰度值
     *
     * @param srcIm
     *            源图片
     * @param x
     *            横坐标
     * @param y
     *            纵坐标
     * @return 灰度值
     */
    public static int getGray(BufferedImage srcIm, int x, int y) {
        int grayValue = getLuminance(srcIm, x, y);
        int newPixel = 0;
        newPixel = (grayValue << 16) & 0x00ff0000 | (newPixel & 0xff00ffff);
        newPixel = (grayValue << 8) & 0x0000ff00 | (newPixel & 0xffff00ff);
        newPixel = (grayValue) & 0x000000ff | (newPixel & 0xffffff00);
        return newPixel;
    }

    /**
     * 获取两个像素点正片叠底后的像素值
     *
     * @param pixel1
     *            像素点1
     * @param pixel2
     *            像素点1
     * @return 新像素点值
     */
    public static int getMultiply(int pixel1, int pixel2) {
        int[] rgb1 = getRGB(pixel1);
        int[] rgb2 = getRGB(pixel2);
        int r = rgb1[0] * rgb2[0] / 255;
        int g = rgb1[1] * rgb2[1] / 255;
        int b = rgb1[2] * rgb2[2] / 255;
        return new Color(r, g, b).getRGB();
    }

    private Colors() {}
}