/* * Copyright (C) 2016 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.internal; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.inject.internal.Errors.checkConfiguration; import static com.google.inject.util.Types.newParameterizedType; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.inject.Binder; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.binder.LinkedBindingBuilder; import com.google.inject.internal.InternalProviderInstanceBindingImpl.InitializationTiming; import com.google.inject.multibindings.MultibindingsTargetVisitor; import com.google.inject.multibindings.OptionalBinderBinding; import com.google.inject.spi.BindingTargetVisitor; import com.google.inject.spi.Dependency; import com.google.inject.spi.Element; import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderWithExtensionVisitor; import com.google.inject.util.Types; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Set; import javax.inject.Qualifier; /** * The actual OptionalBinder plays several roles. It implements Module to hide that fact from the * public API, and installs the various bindings that are exposed to the user. */ public final class RealOptionalBinder<T> implements Module { public static <T> RealOptionalBinder<T> newRealOptionalBinder(Binder binder, Key<T> type) { binder = binder.skipSources(RealOptionalBinder.class); RealOptionalBinder<T> optionalBinder = new RealOptionalBinder<T>(binder, type); binder.install(optionalBinder); return optionalBinder; } /* Reflectively capture java 8's Optional types so we can bind them if we're running in java8. */ private static final Class<?> JAVA_OPTIONAL_CLASS; private static final Object JAVA_OPTIONAL_EMPTY; private static final Method JAVA_OPTIONAL_OF_METHOD; static { Class<?> optional = null; Object emptyObject = null; Method of = null; boolean useJavaOptional = false; try { optional = Class.forName("java.util.Optional"); emptyObject = optional.getDeclaredMethod("empty").invoke(null); of = optional.getDeclaredMethod("of", Object.class); // only use optional support if all our reflection succeeded useJavaOptional = true; } catch (ClassNotFoundException ignored) { } catch (NoSuchMethodException ignored) { } catch (SecurityException ignored) { } catch (IllegalAccessException ignored) { } catch (InvocationTargetException ignored) { } JAVA_OPTIONAL_CLASS = useJavaOptional ? optional : null; JAVA_OPTIONAL_EMPTY = useJavaOptional ? emptyObject : null; JAVA_OPTIONAL_OF_METHOD = useJavaOptional ? of : null; } /** * Returns java.util.Optional.empty() if the parameter is null, calls {@link * #invokeJavaOptionalOf} otherwise. */ private static Object invokeJavaOptionalOfNullable(Object o) { if (o == null) { return JAVA_OPTIONAL_EMPTY; } return invokeJavaOptionalOf(o); } /** Invokes java.util.Optional.of. */ private static Object invokeJavaOptionalOf(Object o) { try { return JAVA_OPTIONAL_OF_METHOD.invoke(null, o); } catch (IllegalAccessException e) { throw new SecurityException(e); } catch (IllegalArgumentException e) { throw new IllegalStateException(e); } catch (InvocationTargetException e) { throw Throwables.propagate(e.getCause()); } } @SuppressWarnings("unchecked") static <T> TypeLiteral<Optional<T>> optionalOf(TypeLiteral<T> type) { return (TypeLiteral<Optional<T>>) TypeLiteral.get(Types.newParameterizedType(Optional.class, type.getType())); } static <T> TypeLiteral<?> javaOptionalOf(TypeLiteral<T> type) { checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found"); return TypeLiteral.get(Types.newParameterizedType(JAVA_OPTIONAL_CLASS, type.getType())); } @SuppressWarnings("unchecked") static <T> TypeLiteral<Optional<javax.inject.Provider<T>>> optionalOfJavaxProvider( TypeLiteral<T> type) { return (TypeLiteral<Optional<javax.inject.Provider<T>>>) TypeLiteral.get( Types.newParameterizedType( Optional.class, newParameterizedType(javax.inject.Provider.class, type.getType()))); } static <T> TypeLiteral<?> javaOptionalOfJavaxProvider(TypeLiteral<T> type) { checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found"); return TypeLiteral.get( Types.newParameterizedType( JAVA_OPTIONAL_CLASS, newParameterizedType(javax.inject.Provider.class, type.getType()))); } @SuppressWarnings("unchecked") static <T> TypeLiteral<Optional<Provider<T>>> optionalOfProvider(TypeLiteral<T> type) { return (TypeLiteral<Optional<Provider<T>>>) TypeLiteral.get( Types.newParameterizedType( Optional.class, newParameterizedType(Provider.class, type.getType()))); } static <T> TypeLiteral<?> javaOptionalOfProvider(TypeLiteral<T> type) { checkState(JAVA_OPTIONAL_CLASS != null, "java.util.Optional not found"); return TypeLiteral.get( Types.newParameterizedType( JAVA_OPTIONAL_CLASS, newParameterizedType(Provider.class, type.getType()))); } @SuppressWarnings("unchecked") static <T> Key<Provider<T>> providerOf(Key<T> key) { Type providerT = Types.providerOf(key.getTypeLiteral().getType()); return (Key<Provider<T>>) key.ofType(providerT); } enum Source { DEFAULT, ACTUAL } @Retention(RUNTIME) @Qualifier @interface Default { String value(); } @Retention(RUNTIME) @Qualifier @interface Actual { String value(); } private final BindingSelection<T> bindingSelection; private final Binder binder; private RealOptionalBinder(Binder binder, Key<T> typeKey) { this.bindingSelection = new BindingSelection<T>(typeKey); this.binder = binder; } /** * Adds a binding for T. Multiple calls to this are safe, and will be collapsed as duplicate * bindings. */ private void addDirectTypeBinding(Binder binder) { binder .bind(bindingSelection.getDirectKey()) .toProvider(new RealDirectTypeProvider<T>(bindingSelection)); } /** * Returns the key to use for the default binding. * * <p>As a side effect this installs support for the 'direct type', so a binding for {@code T} * will be made available. */ Key<T> getKeyForDefaultBinding() { bindingSelection.checkNotInitialized(); addDirectTypeBinding(binder); return bindingSelection.getKeyForDefaultBinding(); } public LinkedBindingBuilder<T> setDefault() { return binder.bind(getKeyForDefaultBinding()); } /** * Returns the key to use for the actual binding, overrides the default if set. * * <p>As a side effect this installs support for the 'direct type', so a binding for {@code T} * will be made available. */ Key<T> getKeyForActualBinding() { bindingSelection.checkNotInitialized(); addDirectTypeBinding(binder); return bindingSelection.getKeyForActualBinding(); } public LinkedBindingBuilder<T> setBinding() { return binder.bind(getKeyForActualBinding()); } @Override public void configure(Binder binder) { bindingSelection.checkNotInitialized(); Key<T> key = bindingSelection.getDirectKey(); // Every OptionalBinder get's the following types bound // * Optional<Provider<T>> // * Optional<javax.inject.Provider<T>> // * Optional<T> // If setDefault() or setBinding() is called then also // * T is bound // If java.util.Optional is on the classpath (because this is a jdk8+ vm), then you also get // * java.util.Optional<Provider<T>> // * java.util.Optional<javax.inject.Provider<T>> // * java.util.Optional<T> InternalProviderInstanceBindingImpl.Factory<Optional<Provider<T>>> optionalProviderFactory = new RealOptionalProviderProvider<T>(bindingSelection); binder .bind(key.ofType(optionalOfProvider(key.getTypeLiteral()))) .toProvider(optionalProviderFactory); // Provider is assignable to javax.inject.Provider and the provider that the factory contains // cannot be modified so we can use some rawtypes hackery to share the same implementation. @SuppressWarnings("unchecked") InternalProviderInstanceBindingImpl.Factory<Optional<javax.inject.Provider<T>>> optionalJavaxProviderFactory = (InternalProviderInstanceBindingImpl.Factory) optionalProviderFactory; binder .bind(key.ofType(optionalOfJavaxProvider(key.getTypeLiteral()))) .toProvider(optionalJavaxProviderFactory); Key<Optional<T>> optionalKey = key.ofType(optionalOf(key.getTypeLiteral())); binder .bind(optionalKey) .toProvider(new RealOptionalKeyProvider<T>(bindingSelection, optionalKey)); // Bind the java-8 types if we know them. bindJava8Optional(binder); } @SuppressWarnings("unchecked") private void bindJava8Optional(Binder binder) { if (JAVA_OPTIONAL_CLASS != null) { Key<?> key = bindingSelection.getDirectKey(); TypeLiteral<?> typeLiteral = key.getTypeLiteral(); InternalProviderInstanceBindingImpl.Factory<Object> javaOptionalProviderFactory = new JavaOptionalProviderProvider(bindingSelection); binder .bind(key.ofType(javaOptionalOfProvider(typeLiteral))) .toProvider((Provider) javaOptionalProviderFactory); // Provider is assignable to javax.inject.Provider and the provider that the factory contains // cannot be modified so we can use some rawtypes hackery to share the same implementation. binder .bind(key.ofType(javaOptionalOfJavaxProvider(typeLiteral))) .toProvider((Provider) javaOptionalProviderFactory); Key<?> javaOptionalKey = key.ofType(javaOptionalOf(typeLiteral)); binder .bind(javaOptionalKey) .toProvider(new JavaOptionalProvider(bindingSelection, javaOptionalKey)); } } /** Provides the binding for java.util.Optional<T>. */ @SuppressWarnings({"rawtypes", "unchecked"}) private static final class JavaOptionalProvider extends RealOptionalBinderProviderWithDependencies implements ProviderWithExtensionVisitor, OptionalBinderBinding { private final Key<?> optionalKey; private Dependency<?> targetDependency; private InternalFactory<?> target; JavaOptionalProvider(BindingSelection<?> bindingSelection, Key<?> optionalKey) { super(bindingSelection); this.optionalKey = optionalKey; } @Override void doInitialize() { if (bindingSelection.getBinding() != null) { target = bindingSelection.getBinding().getInternalFactory(); targetDependency = bindingSelection.getDependency(); } } @Override protected Object doProvision(Errors errors, InternalContext context, Dependency dependency) throws ErrorsException { InternalFactory<?> local = target; if (local == null) { return JAVA_OPTIONAL_EMPTY; } Dependency<?> localDependency = targetDependency; Object result; Dependency previous = context.pushDependency(localDependency, getSource()); try { // See comments in RealOptionalKeyProvider, about how localDependency may be more specific // than what we actually need. result = local.get(errors.withSource(localDependency), context, localDependency, false); } finally { context.popStateAndSetDependency(previous); } return invokeJavaOptionalOfNullable(result); } @Override public Set<Dependency<?>> getDependencies() { return bindingSelection.dependencies; } @SuppressWarnings("unchecked") @Override public Object acceptExtensionVisitor( BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor) visitor).visit(this); } else { return visitor.visit(binding); } } @Override public boolean containsElement(Element element) { return bindingSelection.containsElement(element); } @Override public Binding<?> getActualBinding() { return bindingSelection.getActualBinding(); } @Override public Binding<?> getDefaultBinding() { return bindingSelection.getDefaultBinding(); } @Override public Key getKey() { return optionalKey; } } /** Provides the binding for java.util.Optional<Provider<T>>. */ @SuppressWarnings({"rawtypes", "unchecked"}) private static final class JavaOptionalProviderProvider extends RealOptionalBinderProviderWithDependencies { private Object value; JavaOptionalProviderProvider(BindingSelection<?> bindingSelection) { super(bindingSelection); } @Override void doInitialize() { if (bindingSelection.getBinding() == null) { value = JAVA_OPTIONAL_EMPTY; } else { value = invokeJavaOptionalOf(bindingSelection.getBinding().getProvider()); } } @Override protected Object doProvision(Errors errors, InternalContext context, Dependency dependency) { return value; } @Override public Set<Dependency<?>> getDependencies() { return bindingSelection.providerDependencies(); } } /** Provides the binding for T, conditionally installed by calling setBinding/setDefault. */ private static final class RealDirectTypeProvider<T> extends RealOptionalBinderProviderWithDependencies<T, T> { private Key<? extends T> targetKey; private Object targetSource; private InternalFactory<? extends T> targetFactory; RealDirectTypeProvider(BindingSelection<T> bindingSelection) { super(bindingSelection); } @Override void doInitialize() { BindingImpl<T> targetBinding = bindingSelection.getBinding(); // we only install this factory if they call setBinding()/setDefault() so we know that // targetBinding will be non-null. this.targetKey = targetBinding.getKey(); this.targetSource = targetBinding.getSource(); this.targetFactory = targetBinding.getInternalFactory(); } @Override protected T doProvision(Errors errors, InternalContext context, Dependency<?> dependency) throws ErrorsException { // This is what linked bindings do (see FactoryProxy), and we are pretty similar. context.pushState(targetKey, targetSource); try { return targetFactory.get(errors.withSource(targetKey), context, dependency, true); } finally { context.popState(); } } @Override public Set<Dependency<?>> getDependencies() { return bindingSelection.dependencies; } } /** Provides the binding for Optional<Provider<T>>. */ private static final class RealOptionalProviderProvider<T> extends RealOptionalBinderProviderWithDependencies<T, Optional<Provider<T>>> { private Optional<Provider<T>> value; RealOptionalProviderProvider(BindingSelection<T> bindingSelection) { super(bindingSelection); } @Override void doInitialize() { if (bindingSelection.getBinding() == null) { value = Optional.absent(); } else { value = Optional.of(bindingSelection.getBinding().getProvider()); } } @Override protected Optional<Provider<T>> doProvision( Errors errors, InternalContext context, Dependency<?> dependency) { return value; } @Override public Set<Dependency<?>> getDependencies() { return bindingSelection.providerDependencies(); } } /** Provides the binding for Optional<T>. */ private static final class RealOptionalKeyProvider<T> extends RealOptionalBinderProviderWithDependencies<T, Optional<T>> implements ProviderWithExtensionVisitor<Optional<T>>, OptionalBinderBinding<Optional<T>> { private final Key<Optional<T>> optionalKey; // These are assigned to non-null values during initialization if and only if we have a binding // to delegate to. private Dependency<?> targetDependency; private InternalFactory<? extends T> delegate; RealOptionalKeyProvider(BindingSelection<T> bindingSelection, Key<Optional<T>> optionalKey) { super(bindingSelection); this.optionalKey = optionalKey; } @Override void doInitialize() { if (bindingSelection.getBinding() != null) { delegate = bindingSelection.getBinding().getInternalFactory(); targetDependency = bindingSelection.getDependency(); } } @Override protected Optional<T> doProvision( Errors errors, InternalContext context, Dependency<?> currentDependency) throws ErrorsException { InternalFactory<? extends T> local = delegate; if (local == null) { return Optional.absent(); } Dependency<?> localDependency = targetDependency; T result; Dependency previous = context.pushDependency(localDependency, getSource()); try { // currentDependency is Optional<? super T>, so we really just need to set the target // dependency to ? super T, but we are currently setting it to T. We could hypothetically // make it easier for our delegate to generate proxies by modifying the dependency, but that // would also require us to rewrite the key on each call. So for now we don't do it. result = local.get(errors.withSource(localDependency), context, localDependency, false); } finally { context.popStateAndSetDependency(previous); } return Optional.fromNullable(result); } @Override public Set<Dependency<?>> getDependencies() { return bindingSelection.dependencies(); } @SuppressWarnings("unchecked") @Override public <B, R> R acceptExtensionVisitor( BindingTargetVisitor<B, R> visitor, ProviderInstanceBinding<? extends B> binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor<Optional<T>, R>) visitor).visit(this); } else { return visitor.visit(binding); } } @Override public Key<Optional<T>> getKey() { return optionalKey; } @Override public Binding<?> getActualBinding() { return bindingSelection.getActualBinding(); } @Override public Binding<?> getDefaultBinding() { return bindingSelection.getDefaultBinding(); } @Override public boolean containsElement(Element element) { return bindingSelection.containsElement(element); } } /** * A helper object that implements the core logic for deciding what the implementation of the * binding will be. * * <p>This also implements the main OptionalBinderBinding logic. */ private static final class BindingSelection<T> { private static final ImmutableSet<Dependency<?>> MODULE_DEPENDENCIES = ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class))); /*@Nullable */ BindingImpl<T> actualBinding; /*@Nullable */ BindingImpl<T> defaultBinding; /*@Nullable */ BindingImpl<T> binding; private boolean initialized; private final Key<T> key; // Until the injector initializes us, we don't know what our dependencies are, // so initialize to the whole Injector (like Multibinder, and MapBinder indirectly). private ImmutableSet<Dependency<?>> dependencies = MODULE_DEPENDENCIES; private ImmutableSet<Dependency<?>> providerDependencies = MODULE_DEPENDENCIES; /** lazily allocated, by {@link #getBindingName}. */ private String bindingName; /** lazily allocated, by {@link #getKeyForDefaultBinding}. */ private Key<T> defaultBindingKey; /** lazily allocated, by {@link #getKeyForActualBinding}. */ private Key<T> actualBindingKey; BindingSelection(Key<T> key) { this.key = key; } void checkNotInitialized() { checkConfiguration(!initialized, "already initialized"); } void initialize(InjectorImpl injector) { // Every one of our providers will call this method, so only execute the logic once. if (initialized) { return; } actualBinding = injector.getExistingBinding(getKeyForActualBinding()); defaultBinding = injector.getExistingBinding(getKeyForDefaultBinding()); // We should never create Jit bindings, but we can use them if some other binding created it. BindingImpl<T> userBinding = injector.getExistingBinding(key); if (actualBinding != null) { // TODO(sameb): Consider exposing an option that will allow // ACTUAL to fallback to DEFAULT if ACTUAL's provider returns null. // Right now, an ACTUAL binding can convert from present -> absent // if it's bound to a provider that returns null. binding = actualBinding; } else if (defaultBinding != null) { binding = defaultBinding; } else if (userBinding != null) { // If neither the actual or default is set, then we fallback // to the value bound to the type itself and consider that the // "actual binding" for the SPI. binding = userBinding; actualBinding = userBinding; } if (binding != null) { dependencies = ImmutableSet.<Dependency<?>>of(Dependency.get(binding.getKey())); providerDependencies = ImmutableSet.<Dependency<?>>of(Dependency.get(providerOf(binding.getKey()))); } else { dependencies = ImmutableSet.of(); providerDependencies = ImmutableSet.of(); } initialized = true; } Key<T> getKeyForDefaultBinding() { if (defaultBindingKey == null) { defaultBindingKey = Key.get(key.getTypeLiteral(), new DefaultImpl(getBindingName())); } return defaultBindingKey; } Key<T> getKeyForActualBinding() { if (actualBindingKey == null) { actualBindingKey = Key.get(key.getTypeLiteral(), new ActualImpl(getBindingName())); } return actualBindingKey; } Key<T> getDirectKey() { return key; } private String getBindingName() { // Lazily allocated, most instantiations will never need this because they are deduped during // module installation. if (bindingName == null) { bindingName = Annotations.nameOf(key); } return bindingName; } BindingImpl<T> getBinding() { return binding; } // Provide default implementations for most of the OptionalBinderBinding interface BindingImpl<T> getDefaultBinding() { return defaultBinding; } BindingImpl<T> getActualBinding() { return actualBinding; } ImmutableSet<Dependency<?>> providerDependencies() { return providerDependencies; } ImmutableSet<Dependency<?>> dependencies() { return dependencies; } /** * Returns the Dependency for the target binding, throws NoSuchElementException if no target * exists. * * <p>Calls to this method should typically be guarded by checking if {@link #getBinding()} * returns {@code null}. */ Dependency<?> getDependency() { return Iterables.getOnlyElement(dependencies); } /** Implementation of {@link OptionalBinderBinding#containsElement}. */ boolean containsElement(Element element) { // All of our bindings are ProviderInstanceBindings whose providers extend // RealOptionalBinderProviderWithDependencies and have 'this' as its binding selection. if (element instanceof ProviderInstanceBinding) { javax.inject.Provider<?> providerInstance = ((ProviderInstanceBinding<?>) element).getUserSuppliedProvider(); if (providerInstance instanceof RealOptionalBinderProviderWithDependencies) { return ((RealOptionalBinderProviderWithDependencies<?, ?>) providerInstance) .bindingSelection.equals(this); } } if (element instanceof Binding) { Key<?> elementKey = ((Binding) element).getKey(); // if it isn't one of the things we bound directly it might be an actual or default key return elementKey.equals(getKeyForActualBinding()) || elementKey.equals(getKeyForDefaultBinding()); } return false; // cannot match; } @Override public boolean equals(Object o) { return o instanceof BindingSelection && ((BindingSelection) o).key.equals(key); } @Override public int hashCode() { return key.hashCode(); } } @Override public boolean equals(Object o) { return o instanceof RealOptionalBinder && ((RealOptionalBinder<?>) o).bindingSelection.equals(bindingSelection); } @Override public int hashCode() { return bindingSelection.hashCode(); } /** A base class for ProviderWithDependencies that need equality based on a specific object. */ private abstract static class RealOptionalBinderProviderWithDependencies<T, P> extends InternalProviderInstanceBindingImpl.Factory<P> { protected final BindingSelection<T> bindingSelection; RealOptionalBinderProviderWithDependencies(BindingSelection<T> bindingSelection) { // We need delayed initialization so we can detect jit bindings created by other bindings // while not also creating jit bindings ourselves. This ensures we only pick up user bindings // if the binding would have existed in the injector statically. super(InitializationTiming.DELAYED); this.bindingSelection = bindingSelection; } @Override final void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { bindingSelection.initialize(injector); doInitialize(); } /** * Initialize the factory. BindingSelection is guaranteed to be initialized at this point and * this will be called prior to any provisioning. */ abstract void doInitialize(); @Override public boolean equals(Object obj) { return obj != null && this.getClass() == obj.getClass() && bindingSelection.equals( ((RealOptionalBinderProviderWithDependencies<?, ?>) obj).bindingSelection); } @Override public int hashCode() { return bindingSelection.hashCode(); } } static class DefaultImpl extends BaseAnnotation implements Default { public DefaultImpl(String value) { super(Default.class, value); } } static class ActualImpl extends BaseAnnotation implements Actual { public ActualImpl(String value) { super(Actual.class, value); } } abstract static class BaseAnnotation implements Serializable, Annotation { private final String value; private final Class<? extends Annotation> clazz; BaseAnnotation(Class<? extends Annotation> clazz, String value) { this.clazz = checkNotNull(clazz, "clazz"); this.value = checkNotNull(value, "value"); } public String value() { return this.value; } @Override public int hashCode() { // This is specified in java.lang.Annotation. return (127 * "value".hashCode()) ^ value.hashCode(); } @Override public boolean equals(Object o) { // We check against each annotation type instead of BaseAnnotation // so that we can compare against generated annotation implementations. if (o instanceof Actual && clazz == Actual.class) { Actual other = (Actual) o; return value.equals(other.value()); } else if (o instanceof Default && clazz == Default.class) { Default other = (Default) o; return value.equals(other.value()); } return false; } @Override public String toString() { return "@" + clazz.getName() + (value.isEmpty() ? "" : "(value=" + value + ")"); } @Override public Class<? extends Annotation> annotationType() { return clazz; } private static final long serialVersionUID = 0; } }