001    /*
002     * Copyright 2013 Google Inc.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package com.google.gwtmockito.fakes;
017    
018    import com.google.gwt.core.client.GWT;
019    import com.google.gwt.resources.client.ClientBundle;
020    import com.google.gwt.resources.client.CssResource;
021    import com.google.gwt.resources.client.ResourceCallback;
022    import com.google.gwt.resources.client.ResourcePrototype;
023    import com.google.gwt.safehtml.shared.SafeHtml;
024    import com.google.gwt.safehtml.shared.SafeHtmlUtils;
025    import com.google.gwt.safehtml.shared.SafeUri;
026    import com.google.gwt.safehtml.shared.UriUtils;
027    
028    import java.lang.reflect.InvocationHandler;
029    import java.lang.reflect.Method;
030    import java.lang.reflect.ParameterizedType;
031    import java.lang.reflect.Proxy;
032    
033    /**
034     * Provides fake implementations of {@link ClientBundle}s. Any methods in the
035     * bundle returning {@link CssResource}s will GWT.create the {@link CssResource}
036     * (which by default will cause it to be generated by
037     * {@link FakeMessagesProvider}. Other types of resources will be generated to
038     * return unique values from their getText() or getSafeUri() methods.
039     *
040     * @author ekuefler@google.com (Erik Kuefler)
041     */
042    public class FakeClientBundleProvider implements FakeProvider<ClientBundle> {
043    
044      /**
045       * Returns a new instance of the given type that implements methods as
046       * described in the class description.
047       *
048       * @param type interface to be implemented by the returned type.
049       */
050      @Override
051      public ClientBundle getFake(Class<?> type) {
052        return (ClientBundle) Proxy.newProxyInstance(
053            FakeClientBundleProvider.class.getClassLoader(),
054            new Class<?>[] {type},
055            new InvocationHandler() {
056              @Override
057              public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
058                Class<?> returnType = method.getReturnType();
059                if (CssResource.class.isAssignableFrom(returnType)) {
060                  return GWT.create(returnType);
061                } else {
062                  return createFakeResource(returnType, method.getName());
063                }
064              }
065            });
066      }
067      
068      /**
069       * Creates a fake resource class that returns its own name where possible.
070       */
071      @SuppressWarnings("unchecked") // safe since the proxy implements type
072      private <T> T createFakeResource(Class<T> type, final String name) {
073        return (T) Proxy.newProxyInstance(
074            FakeClientBundleProvider.class.getClassLoader(),
075            new Class<?>[] {type},
076            new InvocationHandler() {
077              @Override
078              public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
079                Class<?> returnType = method.getReturnType();
080                if (returnType == String.class) {
081                  return name;
082                } else if (returnType == SafeHtml.class) {
083                  return SafeHtmlUtils.fromTrustedString(name);
084                } else if (returnType == SafeUri.class) {
085                  return UriUtils.fromTrustedString(name);
086                } else if (returnType == boolean.class) {
087                  return false;
088                } else if (returnType == int.class) {
089                  return 0;
090                } else if (method.getParameterTypes()[0] == ResourceCallback.class) {
091                  // Read the underlying resource type out of the generic parameter
092                  // in the method's argument
093                  Class<?> resourceType = 
094                      (Class<?>) 
095                      ((ParameterizedType) args[0].getClass().getGenericInterfaces()[0])
096                      .getActualTypeArguments()[0];
097                  ((ResourceCallback<ResourcePrototype>) args[0]).onSuccess(
098                      (ResourcePrototype) createFakeResource(resourceType, name));
099                  return null;
100                } else {
101                  throw new IllegalArgumentException(
102                      "Unexpected return type for method " + method.getName());
103                }
104              }
105            });
106      }
107    }