home | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (FactoryProvider.java)

This example Java source code file (FactoryProvider.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

annotation, assistedconstructor, assistedinject, assistinject, class, factoryprovider, invocationhandler, key, list, map, method, object, override, parameter, reflection, typeliteral, util

The FactoryProvider.java Java example source code

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

package com.google.inject.assistedinject;

import static com.google.inject.internal.Annotations.getKey;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.BytecodeGen;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.Message;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <strong>Obsolete. Prefer {@link FactoryModuleBuilder} for its more concise API and
 * additional capability.
 *
 * <p>Provides a factory that combines the caller's arguments with injector-supplied values to
 * construct objects.
 *
 * <h3>Defining a factory
 * Create an interface whose methods return the constructed type, or any of its supertypes. The
 * method's parameters are the arguments required to build the constructed type.
 * <pre>public interface PaymentFactory {
 *   Payment create(Date startDate, Money amount);
 * }</pre>
 * You can name your factory methods whatever you like, such as <i>create, createPayment
 * or <i>newPayment.
 *
 * <h3>Creating a type that accepts factory parameters
 * {@code constructedType} is a concrete class with an {@literal @}{@link Inject}-annotated
 * constructor. In addition to injector-supplied parameters, the constructor should have
 * parameters that match each of the factory method's parameters. Each factory-supplied parameter
 * requires an {@literal @}{@link Assisted} annotation. This serves to document that the parameter
 * is not bound by your application's modules.
 * <pre>public class RealPayment implements Payment {
 *   {@literal @}Inject
 *   public RealPayment(
 *      CreditService creditService,
 *      AuthService authService,
 *      <strong>{@literal @}Assisted Date startDate,
 *      <strong>{@literal @}Assisted Money amount) {
 *     ...
 *   }
 * }</pre>
 * Any parameter that permits a null value should also be annotated {@code @Nullable}.
 *
 * <h3>Configuring factories
 * In your {@link com.google.inject.Module module}, bind the factory interface to the returned
 * factory:
 * <pre>bind(PaymentFactory.class).toProvider(
 *     FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));</pre>
 * As a side-effect of this binding, Guice will inject the factory to initialize it for use. The
 * factory cannot be used until the injector has been initialized.
 *
 * <h3>Using the factory
 * Inject your factory into your application classes. When you use the factory, your arguments
 * will be combined with values from the injector to construct an instance.
 * <pre>public class PaymentAction {
 *   {@literal @}Inject private PaymentFactory paymentFactory;
 *
 *   public void doPayment(Money amount) {
 *     Payment payment = paymentFactory.create(new Date(), amount);
 *     payment.apply();
 *   }
 * }</pre>
 *
 * <h3>Making parameter types distinct
 * The types of the factory method's parameters must be distinct. To use multiple parameters of
 * the same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the
 * parameters. The names must be applied to the factory method's parameters:
 *
 * <pre>public interface PaymentFactory {
 *   Payment create(
 *       <strong>{@literal @}Assisted("startDate") Date startDate,
 *       <strong>{@literal @}Assisted("dueDate") Date dueDate,
 *       Money amount);
 * } </pre>
 * ...and to the concrete type's constructor parameters:
 * <pre>public class RealPayment implements Payment {
 *   {@literal @}Inject
 *   public RealPayment(
 *      CreditService creditService,
 *      AuthService authService,
 *      <strong>{@literal @}Assisted("startDate") Date startDate,
 *      <strong>{@literal @}Assisted("dueDate") Date dueDate,
 *      <strong>{@literal @}Assisted Money amount) {
 *     ...
 *   }
 * }</pre>
 *
 * <h3>Values are created by Guice
 * Returned factories use child injectors to create values. The values are eligible for method
 * interception. In addition, {@literal @}{@literal Inject} members will be injected before they are
 * returned.
 *
 * <h3>Backwards compatibility using {@literal @}AssistedInject
 * Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with
 * {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatability mode.
 *
 * <p>Instead of matching factory method arguments to constructor parameters using their names, the
 * <strong>parameters are matched by their order. The first factory method argument is
 * used for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no
 * effect.
 *
 * <p>Returned values are not created by Guice. These types are not eligible for
 * method interception. They do receive post-construction member injection.
 *
 * @param <F> The factory interface
 *
 * @author jmourits@google.com (Jerome Mourits)
 * @author jessewilson@google.com (Jesse Wilson)
 * @author dtm@google.com (Daniel Martin)
 * 
 * @deprecated use {@link FactoryModuleBuilder} instead.
 */
@Deprecated
public class FactoryProvider<F> implements Provider, HasDependencies {

  /*
   * This class implements the old @AssistedInject implementation that manually matches constructors
   * to factory methods. The new child injector implementation lives in FactoryProvider2.
   */

  private Injector injector;

  private final TypeLiteral<F> factoryType;
  private final TypeLiteral<?> implementationType;
  private final Map<Method, AssistedConstructor factoryMethodToConstructor;

  public static <F> Provider newFactory(Class factoryType, Class implementationType){
    return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType));
  }

  public static <F> Provider newFactory(
      TypeLiteral<F> factoryType, TypeLiteral implementationType) {
    Map<Method, AssistedConstructor factoryMethodToConstructor
        = createMethodMapping(factoryType, implementationType);

    if (!factoryMethodToConstructor.isEmpty()) {
      return new FactoryProvider<F>(factoryType, implementationType, factoryMethodToConstructor);
    } else {
      BindingCollector collector = new BindingCollector();

      // Preserving backwards-compatibility:  Map all return types in a factory
      // interface to the passed implementation type.
      Errors errors = new Errors();
      Key<?> implementationKey = Key.get(implementationType);

      try {
        for (Method method : factoryType.getRawType().getMethods()) {
          Key<?> returnType = getKey(factoryType.getReturnType(method), method,
              method.getAnnotations(), errors);
          if (!implementationKey.equals(returnType)) {
            collector.addBinding(returnType, implementationType);
          }
      }
      } catch (ErrorsException e) {
        throw new ConfigurationException(e.getErrors().getMessages());
      }

      return new FactoryProvider2<F>(Key.get(factoryType), collector);
    }
  }

  private FactoryProvider(TypeLiteral<F> factoryType,
      TypeLiteral<?> implementationType,
      Map<Method, AssistedConstructor factoryMethodToConstructor) {
    this.factoryType = factoryType;
    this.implementationType = implementationType;
    this.factoryMethodToConstructor = factoryMethodToConstructor;
    checkDeclaredExceptionsMatch();
  }

  @Inject
  void setInjectorAndCheckUnboundParametersAreInjectable(Injector injector) {
    this.injector = injector;
    for (AssistedConstructor<?> c : factoryMethodToConstructor.values()) {
      for (Parameter p : c.getAllParameters()) {
        if(!p.isProvidedByFactory() && !paramCanBeInjected(p, injector)) {
          // this is lame - we're not using the proper mechanism to add an
          // error to the injector. Throughout this class we throw exceptions
          // to add errors, which isn't really the best way in Guice
          throw newConfigurationException("Parameter of type '%s' is not injectable or annotated "
                + "with @Assisted for Constructor '%s'", p, c);
        }
      }
    }
  }

  private void checkDeclaredExceptionsMatch() {
    for (Map.Entry<Method, AssistedConstructor entry : factoryMethodToConstructor.entrySet()) {
      for (Class<?> constructorException : entry.getValue().getDeclaredExceptions()) {
        if (!isConstructorExceptionCompatibleWithFactoryExeception(
            constructorException, entry.getKey().getExceptionTypes())) {
          throw newConfigurationException("Constructor %s declares an exception, but no compatible "
              + "exception is thrown by the factory method %s", entry.getValue(), entry.getKey());
        }
      }
    }
  }

  private boolean isConstructorExceptionCompatibleWithFactoryExeception(
      Class<?> constructorException, Class[] factoryExceptions) {
    for (Class<?> factoryException : factoryExceptions) {
      if (factoryException.isAssignableFrom(constructorException)) {
        return true;
      }
    }
    return false;
  }

  private boolean paramCanBeInjected(Parameter parameter, Injector injector) {
    return parameter.isBound(injector);
  }

  private static Map<Method, AssistedConstructor createMethodMapping(
      TypeLiteral<?> factoryType, TypeLiteral implementationType) {
    List<AssistedConstructor constructors = Lists.newArrayList();

    for (Constructor<?> constructor : implementationType.getRawType().getDeclaredConstructors()) {
      if (constructor.isAnnotationPresent(AssistedInject.class)) {
        AssistedConstructor<?> assistedConstructor = AssistedConstructor.create(
            constructor, implementationType.getParameterTypes(constructor));
        constructors.add(assistedConstructor);
      }
    }

    if (constructors.isEmpty()) {
      return ImmutableMap.of();
    }

    Method[] factoryMethods = factoryType.getRawType().getMethods();

    if (constructors.size() != factoryMethods.length) {
      throw newConfigurationException("Constructor mismatch: %s has %s @AssistedInject "
          + "constructors, factory %s has %s creation methods", implementationType,
          constructors.size(), factoryType, factoryMethods.length);
    }

    Map<ParameterListKey, AssistedConstructor paramsToConstructor = Maps.newHashMap();

    for (AssistedConstructor<?> c : constructors) {
      if (paramsToConstructor.containsKey(c.getAssistedParameters())) {
        throw new RuntimeException("Duplicate constructor, " + c);
      }
      paramsToConstructor.put(c.getAssistedParameters(), c);
    }

    Map<Method, AssistedConstructor result = Maps.newHashMap();
    for (Method method : factoryMethods) {
      if (!method.getReturnType().isAssignableFrom(implementationType.getRawType())) {
        throw newConfigurationException("Return type of method %s is not assignable from %s",
            method, implementationType);
      }

      List<Type> parameterTypes = Lists.newArrayList();
      for (TypeLiteral<?> parameterType : factoryType.getParameterTypes(method)) {
        parameterTypes.add(parameterType.getType());
      }
      ParameterListKey methodParams = new ParameterListKey(parameterTypes);

      if (!paramsToConstructor.containsKey(methodParams)) {
        throw newConfigurationException("%s has no @AssistInject constructor that takes the "
            + "@Assisted parameters %s in that order. @AssistInject constructors are %s",
            implementationType, methodParams, paramsToConstructor.values());
      }

      method.getParameterAnnotations();
      for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) {
        for (Annotation parameterAnnotation : parameterAnnotations) {
          if (parameterAnnotation.annotationType() == Assisted.class) {
            throw newConfigurationException("Factory method %s has an @Assisted parameter, which "
                + "is incompatible with the deprecated @AssistedInject annotation. Please replace "
                + "@AssistedInject with @Inject on the %s constructor.",
                method, implementationType);
          }
        }
      }

      AssistedConstructor<?> matchingConstructor = paramsToConstructor.remove(methodParams);

      result.put(method, matchingConstructor);
    }
    return result;
  }

  public Set<Dependency getDependencies() {
    List<Dependency dependencies = Lists.newArrayList();
    for (AssistedConstructor<?> constructor : factoryMethodToConstructor.values()) {
      for (Parameter parameter : constructor.getAllParameters()) {
        if (!parameter.isProvidedByFactory()) {
          dependencies.add(Dependency.get(parameter.getPrimaryBindingKey()));
        }
      }
    }
    return ImmutableSet.copyOf(dependencies);
  }

  public F get() {
    InvocationHandler invocationHandler = new InvocationHandler() {
      public Object invoke(Object proxy, Method method, Object[] creationArgs) throws Throwable {
        // pass methods from Object.class to the proxy
        if (method.getDeclaringClass().equals(Object.class)) {
          if ("equals".equals(method.getName())) {
            return proxy == creationArgs[0];
          } else if ("hashCode".equals(method.getName())) {
            return System.identityHashCode(proxy);
          } else {
            return method.invoke(this, creationArgs);
          }
        }

        AssistedConstructor<?> constructor = factoryMethodToConstructor.get(method);
        Object[] constructorArgs = gatherArgsForConstructor(constructor, creationArgs);
        Object objectToReturn = constructor.newInstance(constructorArgs);
        injector.injectMembers(objectToReturn);
        return objectToReturn;
      }

      public Object[] gatherArgsForConstructor(
          AssistedConstructor<?> constructor,
          Object[] factoryArgs) {
        int numParams = constructor.getAllParameters().size();
        int argPosition = 0;
        Object[] result = new Object[numParams];

        for (int i = 0; i < numParams; i++) {
          Parameter parameter = constructor.getAllParameters().get(i);
          if (parameter.isProvidedByFactory()) {
            result[i] = factoryArgs[argPosition];
            argPosition++;
          } else {
            result[i] = parameter.getValue(injector);
          }
        }
        return result;
      }
    };

    @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class<T>
    Class<F> factoryRawType = (Class) (Class) factoryType.getRawType();
    return factoryRawType.cast(Proxy.newProxyInstance(BytecodeGen.getClassLoader(factoryRawType),
        new Class[] { factoryRawType }, invocationHandler));
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(factoryType, implementationType);
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof FactoryProvider)) {
      return false;
    }
    FactoryProvider<?> other = (FactoryProvider) obj;
    return factoryType.equals(other.factoryType)
        && implementationType.equals(other.implementationType);
  }

  private static ConfigurationException newConfigurationException(String format, Object... args) {
    return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args))));
  }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java FactoryProvider.java source code file:



my book on functional programming

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.