content: /*
   * Copyright (C) 2020 ActiveJ LLC.
   *
   * 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 io.activej.inject.impl;
  
  import io.activej.inject.Key;
  import io.activej.inject.binding.Binding;
  
  import java.util.Collection;
  import java.util.List;
  import java.util.Set;
  import java.util.concurrent.atomic.AtomicReferenceArray;
  
  import static java.util.stream.Collectors.toSet;
  
  /**
   * This is a {@link Binding} binding modifying function, that can add extra dependencies to it
   * and run initialization code for instance after it was created.
   */
  @SuppressWarnings({"rawtypes", "Convert2Lambda"})
  public abstract class BindingInitializer<T> {
  	private static final BindingInitializer<?> NOOP = new BindingInitializer<>(Set.of()) {
  		@Override
  		public CompiledBindingInitializer<Object> compile(CompiledBindingLocator compiledBindings) {
  			return new CompiledBindingInitializer<>() {
  				@Override
  				public void initInstance(Object instance, AtomicReferenceArray[] instances, int synchronizedScope) {
  					// noop
  				}
  			};
  		}
  	};
  
  	private final Set<Key<?>> dependencies;
  
  	protected BindingInitializer(Set<Key<?>> dependencies) {
  		this.dependencies = dependencies;
  	}
  
  	public Set<Key<?>> getDependencies() {
  		return dependencies;
  	}
  
  	public abstract CompiledBindingInitializer<T> compile(CompiledBindingLocator compiledBindings);
  
  	@SafeVarargs
  	public static <T> BindingInitializer<T> combine(BindingInitializer<T>... bindingInitializers) {
  		return combine(List.of(bindingInitializers));
  	}
  
  	@SuppressWarnings("unchecked")
  	public static <T> BindingInitializer<T> combine(List<BindingInitializer<T>> bindingInitializers) {
  		return new BindingInitializer<>(bindingInitializers.stream().map(BindingInitializer::getDependencies).flatMap(Collection::stream).collect(toSet())) {
  			@Override
  			public CompiledBindingInitializer<T> compile(CompiledBindingLocator compiledBindings) {
  				CompiledBindingInitializer<T>[] initializers = bindingInitializers.stream()
  					.filter(bindingInitializer -> bindingInitializer != NOOP)
  					.map(bindingInitializer -> bindingInitializer.compile(compiledBindings))
  					.toArray(CompiledBindingInitializer[]::new);
  				if (initializers.length == 0) return new CompiledBindingInitializer<>() {
  					@Override
  					public void initInstance(T instance, AtomicReferenceArray[] instances, int synchronizedScope) {
  						// noop
  					}
  				};
  				if (initializers.length == 1) return initializers[0];
  				if (initializers.length == 2) {
  					CompiledBindingInitializer<T> initializer0 = initializers[0];
  					CompiledBindingInitializer<T> initializer1 = initializers[1];
  					return new CompiledBindingInitializer<>() {
  						@Override
  						public void initInstance(T instance, AtomicReferenceArray[] instances, int synchronizedScope) {
  							initializer0.initInstance(instance, instances, synchronizedScope);
  							initializer1.initInstance(instance, instances, synchronizedScope);
  						}
  					};
  				}
  				return new CompiledBindingInitializer<>() {
  					@Override
  					public void initInstance(T instance, AtomicReferenceArray[] instances, int synchronizedScope) {
  						//noinspection ForLoopReplaceableByForEach
  						for (int i = 0; i < initializers.length; i++) {
  							initializers[i].initInstance(instance, instances, synchronizedScope);
  						}
  					}
  				};
  			}
  		};
  	}
  
  	@SuppressWarnings("unchecked")
  	public static <T> BindingInitializer<T> noop() {
  		return (BindingInitializer<T>) NOOP;
  	}
  }
  