content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.aries.blueprint.container;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Properties;
  import java.util.Set;
  
  import org.apache.aries.blueprint.ComponentDefinitionRegistry;
  import org.apache.aries.blueprint.ExtendedBeanMetadata;
  import org.apache.aries.blueprint.ExtendedReferenceMetadata;
  import org.apache.aries.blueprint.ExtendedServiceReferenceMetadata;
  import org.apache.aries.blueprint.PassThroughMetadata;
  import org.apache.aries.blueprint.di.ArrayRecipe;
  import org.apache.aries.blueprint.di.CollectionRecipe;
  import org.apache.aries.blueprint.di.ComponentFactoryRecipe;
  import org.apache.aries.blueprint.di.DependentComponentFactoryRecipe;
  import org.apache.aries.blueprint.di.IdRefRecipe;
  import org.apache.aries.blueprint.di.MapRecipe;
  import org.apache.aries.blueprint.di.PassThroughRecipe;
  import org.apache.aries.blueprint.di.Recipe;
  import org.apache.aries.blueprint.di.RefRecipe;
  import org.apache.aries.blueprint.di.ValueRecipe;
  import org.apache.aries.blueprint.ext.ComponentFactoryMetadata;
  import org.apache.aries.blueprint.ext.DependentComponentFactoryMetadata;
  import org.apache.aries.blueprint.mutable.MutableMapMetadata;
  import org.apache.aries.blueprint.reflect.MetadataUtil;
  import org.apache.aries.blueprint.utils.ServiceListener;
  import org.osgi.service.blueprint.reflect.BeanArgument;
  import org.osgi.service.blueprint.reflect.BeanMetadata;
  import org.osgi.service.blueprint.reflect.BeanProperty;
  import org.osgi.service.blueprint.reflect.CollectionMetadata;
  import org.osgi.service.blueprint.reflect.ComponentMetadata;
  import org.osgi.service.blueprint.reflect.IdRefMetadata;
  import org.osgi.service.blueprint.reflect.MapEntry;
  import org.osgi.service.blueprint.reflect.MapMetadata;
  import org.osgi.service.blueprint.reflect.Metadata;
  import org.osgi.service.blueprint.reflect.NullMetadata;
  import org.osgi.service.blueprint.reflect.PropsMetadata;
  import org.osgi.service.blueprint.reflect.RefMetadata;
  import org.osgi.service.blueprint.reflect.ReferenceListMetadata;
  import org.osgi.service.blueprint.reflect.ReferenceListener;
  import org.osgi.service.blueprint.reflect.ReferenceMetadata;
  import org.osgi.service.blueprint.reflect.RegistrationListener;
  import org.osgi.service.blueprint.reflect.ServiceMetadata;
  import org.osgi.service.blueprint.reflect.ValueMetadata;
  
  /**
   * TODO: javadoc
   *
   * @version $Rev$, $Date$
   */
  public class RecipeBuilder {
  
      private final Set<String> names = new HashSet<String>();
      private final BlueprintContainerImpl blueprintContainer;
      private final ComponentDefinitionRegistry registry;
      private final IdSpace recipeIdSpace;
  
      public RecipeBuilder(BlueprintContainerImpl blueprintContainer, IdSpace recipeIdSpace) {
          this.recipeIdSpace = recipeIdSpace;
          this.blueprintContainer = blueprintContainer;
          this.registry = blueprintContainer.getComponentDefinitionRegistry();
      }
      
      public BlueprintRepository createRepository() {
          BlueprintRepository repository = new BlueprintRepository(blueprintContainer);
          // Create component recipes
          for (String name : registry.getComponentDefinitionNames()) {
              ComponentMetadata component = registry.getComponentDefinition(name);
              Recipe recipe = createRecipe(component);
              repository.putRecipe(recipe.getName(), recipe);
          }
          repository.validate();
          return repository;
      }
  
      public Recipe createRecipe(ComponentMetadata component) {
  
          // Custom components should be handled before built-in ones
          // in case we have a custom component that also implements a built-in metadata
          
          if (component instanceof DependentComponentFactoryMetadata) { 
              return createDependentComponentFactoryMetadata((DependentComponentFactoryMetadata) component);
          } else if (component instanceof ComponentFactoryMetadata) {
              return createComponentFactoryMetadata((ComponentFactoryMetadata) component);
          } else if (component instanceof BeanMetadata) {
              return createBeanRecipe((BeanMetadata) component);
          } else if (component instanceof ServiceMetadata) {
              return createServiceRecipe((ServiceMetadata) component);
          } else if (component instanceof ReferenceMetadata) {
              return createReferenceRecipe((ReferenceMetadata) component);
          } else if (component instanceof ReferenceListMetadata) {
              return createReferenceListRecipe((ReferenceListMetadata) component);
          } else if (component instanceof PassThroughMetadata) {
              return createPassThroughRecipe((PassThroughMetadata) component);
          } else {
              throw new IllegalStateException("Unsupported component type " + component.getClass());
          }
      }
  
      private Recipe createComponentFactoryMetadata(ComponentFactoryMetadata metadata) {
          return new ComponentFactoryRecipe<ComponentFactoryMetadata>(
                  metadata.getId(), metadata, blueprintContainer, getDependencies(metadata));
      }
      
      private Recipe createDependentComponentFactoryMetadata(DependentComponentFactoryMetadata metadata) {
          return new DependentComponentFactoryRecipe(
                  metadata.getId(), metadata, blueprintContainer, getDependencies(metadata));
      }
      
      private List<Recipe> getDependencies(ComponentMetadata metadata) {
          List<Recipe> deps = new ArrayList<Recipe>();
          for (String name : metadata.getDependsOn()) {
              deps.add(new RefRecipe(getName(null), name));
          }
          return deps;
      }
  
      private Recipe createPassThroughRecipe(PassThroughMetadata passThroughMetadata) {
          return new PassThroughRecipe(getName(passThroughMetadata.getId()), 
              passThroughMetadata.getObject());
      }
  
      private Recipe createReferenceListRecipe(ReferenceListMetadata metadata) {
          ValueRecipe filterRecipe = null;
          if (metadata instanceof ExtendedReferenceMetadata) {
              ValueMetadata filterMetadata = ((ExtendedServiceReferenceMetadata) metadata).getExtendedFilter();
              if (filterMetadata != null) {
                  filterRecipe = (ValueRecipe) getValue(filterMetadata, null);
              }
          }
          CollectionRecipe listenersRecipe = null;
          if (metadata.getReferenceListeners() != null) {
              listenersRecipe = new CollectionRecipe(getName(null), ArrayList.class, Object.class.getName());
              for (ReferenceListener listener : metadata.getReferenceListeners()) {
                  listenersRecipe.add(createRecipe(listener));
              }
          }
          ReferenceListRecipe recipe = new ReferenceListRecipe(getName(metadata.getId()),
                                                   blueprintContainer,
                                                   metadata,
                                                   filterRecipe,
                                                   listenersRecipe,
                                                   getDependencies(metadata));
          return recipe;
      }
  
      private ReferenceRecipe createReferenceRecipe(ReferenceMetadata metadata) {
          ValueRecipe filterRecipe = null;
          if (metadata instanceof ExtendedReferenceMetadata) {
              ValueMetadata filterMetadata = ((ExtendedServiceReferenceMetadata) metadata).getExtendedFilter();
              if (filterMetadata != null) {
                  filterRecipe = (ValueRecipe) getValue(filterMetadata, null);
              }
          }
          CollectionRecipe listenersRecipe = null;
          if (metadata.getReferenceListeners() != null) {
              listenersRecipe = new CollectionRecipe(getName(null), ArrayList.class, Object.class.getName());
              for (ReferenceListener listener : metadata.getReferenceListeners()) {
                  listenersRecipe.add(createRecipe(listener));
              }
          }
          ReferenceRecipe recipe = new ReferenceRecipe(getName(metadata.getId()),
                                                       blueprintContainer,
                                                       metadata,
                                                       filterRecipe,
                                                       listenersRecipe,
                                                       getDependencies(metadata));
          return recipe;
      }
  
      private Recipe createServiceRecipe(ServiceMetadata serviceExport) {
          CollectionRecipe listenersRecipe = new CollectionRecipe(getName(null), ArrayList.class, Object.class.getName());
          if (serviceExport.getRegistrationListeners() != null) {
              for (RegistrationListener listener : serviceExport.getRegistrationListeners()) {
                  listenersRecipe.add(createRecipe(listener));
              }
          }
          ServiceRecipe recipe = new ServiceRecipe(getName(serviceExport.getId()),
                                                   blueprintContainer,
                                                   serviceExport,
                                                   getValue(serviceExport.getServiceComponent(), null),
                                                   listenersRecipe,
                                                   getServicePropertiesRecipe(serviceExport),
                                                   getDependencies(serviceExport));
          return recipe;
      }
  
      protected MapRecipe getServicePropertiesRecipe(ServiceMetadata metadata) {
          List<MapEntry> properties = metadata.getServiceProperties();
          if (properties != null) {
              MutableMapMetadata map = MetadataUtil.createMetadata(MutableMapMetadata.class);
              for (MapEntry e : properties) {
                  map.addEntry(e);
              }
              return createMapRecipe(map);
          } else {
              return null;
          }
      }
      
      private Object getBeanClass(BeanMetadata beanMetadata) {
          if (beanMetadata instanceof ExtendedBeanMetadata) {
              ExtendedBeanMetadata extBeanMetadata = (ExtendedBeanMetadata) beanMetadata;
              if (extBeanMetadata.getRuntimeClass() != null) {
                  return extBeanMetadata.getRuntimeClass();
              }
          }
          return beanMetadata.getClassName();        
      }
  
      private boolean allowFieldInjection(BeanMetadata beanMetadata) {
          if (beanMetadata instanceof ExtendedBeanMetadata) {
              return ((ExtendedBeanMetadata) beanMetadata).getFieldInjection();
          }
          return false;
      }
  
      private boolean allowRawConversion(BeanMetadata beanMetadata) {
          if (beanMetadata instanceof ExtendedBeanMetadata) {
              return ((ExtendedBeanMetadata) beanMetadata).getRawConversion();
          }
          return false;
      }
  
      private boolean allowNonStandardSetters(BeanMetadata beanMetadata) {
          if (beanMetadata instanceof ExtendedBeanMetadata) {
              return ((ExtendedBeanMetadata) beanMetadata).getNonStandardSetters();
          }
          return false;
      }
  
      private BeanRecipe createBeanRecipe(BeanMetadata beanMetadata) {
          BeanRecipe recipe = new BeanRecipe(
                  getName(beanMetadata.getId()),
                  blueprintContainer,
                  getBeanClass(beanMetadata),
                  allowFieldInjection(beanMetadata),
                  allowRawConversion(beanMetadata),
                  allowNonStandardSetters(beanMetadata));
          // Create refs for explicit dependencies
          recipe.setExplicitDependencies(getDependencies(beanMetadata));
          recipe.setPrototype(MetadataUtil.isPrototypeScope(beanMetadata) || MetadataUtil.isCustomScope(beanMetadata));
          recipe.setInitMethod(beanMetadata.getInitMethod());
          recipe.setDestroyMethod(beanMetadata.getDestroyMethod());
          recipe.setInterceptorLookupKey(beanMetadata);
          List<BeanArgument> beanArguments = beanMetadata.getArguments();
          if (beanArguments != null && !beanArguments.isEmpty()) {
              boolean hasIndex = (beanArguments.get(0).getIndex() >= 0);
              if (hasIndex) {
                  List<BeanArgument> beanArgumentsCopy = new ArrayList<BeanArgument>(beanArguments);
                  Collections.sort(beanArgumentsCopy, MetadataUtil.BEAN_COMPARATOR);
                  beanArguments = beanArgumentsCopy;
              }
              List<Object> arguments = new ArrayList<Object>();
              List<String> argTypes = new ArrayList<String>();
              for (BeanArgument argument : beanArguments) {
                  Recipe value = getValue(argument.getValue(), null);
                  arguments.add(value);
                  argTypes.add(argument.getValueType());
              }
              recipe.setArguments(arguments);
              recipe.setArgTypes(argTypes);
              recipe.setReorderArguments(!hasIndex);
          }
          recipe.setFactoryMethod(beanMetadata.getFactoryMethod());
          if (beanMetadata.getFactoryComponent() != null) {
              recipe.setFactoryComponent(getValue(beanMetadata.getFactoryComponent(), null));
          }
          for (BeanProperty property : beanMetadata.getProperties()) {
              Recipe value = getValue(property.getValue(), null);
              recipe.setProperty(property.getName(), value);
          }
          return recipe;
      }
  
      private Recipe createRecipe(RegistrationListener listener) {
          BeanRecipe recipe = new BeanRecipe(getName(null), blueprintContainer, ServiceListener.class, false, false, false);
          recipe.setProperty("listener", getValue(listener.getListenerComponent(), null));
          if (listener.getRegistrationMethod() != null) {
              recipe.setProperty("registerMethod", listener.getRegistrationMethod());
          }
          if (listener.getUnregistrationMethod() != null) {
              recipe.setProperty("unregisterMethod", listener.getUnregistrationMethod());
          }
          recipe.setProperty("blueprintContainer", blueprintContainer);
          return recipe;
      }
  
      private Recipe createRecipe(ReferenceListener listener) {
          BeanRecipe recipe = new BeanRecipe(getName(null), blueprintContainer, AbstractServiceReferenceRecipe.Listener.class, false, false, false);
          recipe.setProperty("listener", getValue(listener.getListenerComponent(), null));
          recipe.setProperty("metadata", listener);
          recipe.setProperty("blueprintContainer", blueprintContainer);
          return recipe;
      }
  
      private Recipe getValue(Metadata v, Object groupingType) {
          if (v instanceof NullMetadata) {
              return null;
          } else if (v instanceof ComponentMetadata) {
              return createRecipe((ComponentMetadata) v);
          } else if (v instanceof ValueMetadata) {
              ValueMetadata stringValue = (ValueMetadata) v;
              Object type = stringValue.getType();
              type = (type == null) ? groupingType : type;
              ValueRecipe vr = new ValueRecipe(getName(null), stringValue, type);
              return vr;
          } else if (v instanceof RefMetadata) {
              // TODO: make it work with property-placeholders?
              String componentName = ((RefMetadata) v).getComponentId();
              RefRecipe rr = new RefRecipe(getName(null), componentName);
              return rr;
          } else if (v instanceof CollectionMetadata) {
              CollectionMetadata collectionMetadata = (CollectionMetadata) v;
              Class<?> cl = collectionMetadata.getCollectionClass();
              String type = collectionMetadata.getValueType();
              if (cl == Object[].class) {
                  ArrayRecipe ar = new ArrayRecipe(getName(null), type);
                  for (Metadata lv : collectionMetadata.getValues()) {
                      ar.add(getValue(lv, type));
                  }
                  return ar;
              } else {
                  CollectionRecipe cr = new CollectionRecipe(getName(null), cl != null ? cl : ArrayList.class, type);
                  for (Metadata lv : collectionMetadata.getValues()) {
                      cr.add(getValue(lv, type));
                  }
                  return cr;
              }
          } else if (v instanceof MapMetadata) {
              return createMapRecipe((MapMetadata) v);
          } else if (v instanceof PropsMetadata) {
              PropsMetadata mapValue = (PropsMetadata) v;
              MapRecipe mr = new MapRecipe(getName(null), Properties.class, String.class, String.class);
              for (MapEntry entry : mapValue.getEntries()) {
                  Recipe key = getValue(entry.getKey(), String.class);
                  Recipe val = getValue(entry.getValue(), String.class);
                  mr.put(key, val);
              }
              return mr;
          } else if (v instanceof IdRefMetadata) {
              // TODO: make it work with property-placeholders?
              String componentName = ((IdRefMetadata) v).getComponentId();
              IdRefRecipe rnr = new IdRefRecipe(getName(null), componentName);
              return rnr;
          } else {
              throw new IllegalStateException("Unsupported value: " + (v != null ? v.getClass().getName() : "null"));
          }
      }
  
      private MapRecipe createMapRecipe(MapMetadata mapValue) {
          String keyType = mapValue.getKeyType();
          String valueType = mapValue.getValueType();
          MapRecipe mr = new MapRecipe(getName(null), HashMap.class, keyType, valueType);
          for (MapEntry entry : mapValue.getEntries()) {
              Recipe key = getValue(entry.getKey(), keyType);
              Recipe val = getValue(entry.getValue(), valueType);
              mr.put(key, val);
          }
          return mr;
      }
  
      private String getName(String name) {
          if (name == null) {
              do {
                  name = "#recipe-" + recipeIdSpace.nextId();
              } while (names.contains(name) || registry.containsComponentDefinition(name));
          }
          names.add(name);
          return name;
      }
  
  }
  