package aQute.bnd.component;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.FieldOption;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferenceScope;
import org.osgi.service.component.annotations.ServiceScope;
import org.osgi.service.metatype.annotations.Designate;

import aQute.bnd.annotation.xml.XMLAttribute;
import aQute.bnd.component.DSAnnotations.Options;
import aQute.bnd.component.error.DeclarativeServicesAnnotationError;
import aQute.bnd.component.error.DeclarativeServicesAnnotationError.ErrorType;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Annotation;
import aQute.bnd.osgi.ClassDataCollector;
import aQute.bnd.osgi.Clazz;
import aQute.bnd.osgi.Clazz.FieldDef;
import aQute.bnd.osgi.Clazz.MethodDef;
import aQute.bnd.osgi.Descriptors;
import aQute.bnd.osgi.Descriptors.TypeRef;
import aQute.bnd.osgi.Verifier;
import aQute.bnd.version.Version;
import aQute.bnd.xmlattribute.XMLAttributeFinder;
import aQute.lib.collections.MultiMap;

/**
 * Processes spec DS annotations into xml.
 */
public class AnnotationReader extends ClassDataCollector {
	final static TypeRef[]				EMPTY						= new TypeRef[0];
	final static Pattern				PROPERTY_PATTERN			= Pattern.compile(
			"\\s*([^=\\s:]+)\\s*(?::\\s*(Boolean|Byte|Character|Short|Integer|Long|Float|Double|String)\\s*)?=(.*)");

	public static final Version			V1_0						= new Version("1.0.0");																											// "1.0.0"
	public static final Version			V1_1						= new Version("1.1.0");																											// "1.1.0"
	public static final Version			V1_2						= new Version("1.2.0");																											// "1.2.0"
	public static final Version			V1_3						= new Version("1.3.0");																											// "1.3.0"
	public static final Version			V1_4						= new Version("1.4.0");																											// "1.3.0"

	static final Pattern				BINDNAME					= Pattern.compile("(set|add|bind)?(.*)");

	static final Pattern				BINDDESCRIPTORDS10			= Pattern.compile(
			"\\(L(((org/osgi/framework/ServiceReference)|(org/osgi/service/component/ComponentServiceObjects)|(java/util/Map\\$Entry)|(java/util/Map))|([^;]+));\\)(V|(Ljava/util/Map;))");
	static final Pattern				BINDDESCRIPTORDS11			= Pattern
			.compile("\\(L([^;]+);(Ljava/util/Map;)?\\)(V|(Ljava/util/Map;))");

	// includes support for felix extensions
	static final Pattern				BINDDESCRIPTORDS13			= Pattern.compile(
			"\\(((Lorg/osgi/framework/ServiceReference;)|(Lorg/osgi/service/component/ComponentServiceObjects;)|(Ljava/util/Map;)|(Ljava/util/Map\\$Entry;)|(L([^;]+);))+\\)(V|(Ljava/util/Map;))");

	static final Pattern				LIFECYCLEDESCRIPTORDS10		= Pattern
			.compile("\\((Lorg/osgi/service/component/ComponentContext;)\\)(V|(Ljava/util/Map;))");
	static final Pattern				LIFECYCLEDESCRIPTORDS11		= Pattern.compile(
			"\\(((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;))*\\)(V|(Ljava/util/Map;))");
	static final Pattern				LIFECYCLEDESCRIPTORDS13		= Pattern
			.compile("\\((L([^;]+);)*\\)(V|(Ljava/util/Map;))");
	static final Pattern				LIFECYCLEARGUMENT			= Pattern.compile(
			"((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;)|(L([^;]+);))");

	static final Pattern				IDENTIFIERTOPROPERTY		= Pattern
			.compile("(__)|(_)|(\\$_\\$)|(\\$\\$)|(\\$)");

	static final Pattern				DEACTIVATEDESCRIPTORDS11	= Pattern.compile(
			"\\(((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;)|(Ljava/lang/Integer;)|(I))*\\)(V|(Ljava/util/Map;))");
	static final Pattern				DEACTIVATEDESCRIPTORDS13	= Pattern
			.compile("\\(((L([^;]+);)|(I))*\\)(V|(Ljava/util/Map;))");

	final static Map<String,Class< ? >>	wrappers;

	static {
		Map<String,Class< ? >> map = new HashMap<String,Class< ? >>();
		map.put("boolean", Boolean.class);
		map.put("byte", Byte.class);
		map.put("short", Short.class);
		map.put("char", Character.class);
		map.put("int", Integer.class);
		map.put("long", Long.class);
		map.put("float", Float.class);
		map.put("double", Double.class);
		wrappers = Collections.unmodifiableMap(map);
	}

	ComponentDef											component;

	Clazz													clazz;
	TypeRef													interfaces[];
	FieldDef												member;
	TypeRef													className;
	Analyzer												analyzer;
	MultiMap<String,Clazz.MethodDef>						methods					= new MultiMap<String,Clazz.MethodDef>();
	TypeRef													extendsClass;
	boolean													baseclass				= true;
	final EnumSet<Options>									options;

	final Map<FieldDef,ReferenceDef>						referencesByMember		= new HashMap<FieldDef,ReferenceDef>();

	final XMLAttributeFinder								finder;

	Map<String,List<DeclarativeServicesAnnotationError>>	mismatchedAnnotations	= new HashMap<String,List<DeclarativeServicesAnnotationError>>();

	AnnotationReader(Analyzer analyzer, Clazz clazz, EnumSet<Options> options, XMLAttributeFinder finder, Version minVersion) {
		this.analyzer = analyzer;
		this.clazz = clazz;
		this.options = options;
		this.finder = finder;
		this.component = new ComponentDef(finder, minVersion);
	}

	public static ComponentDef getDefinition(Clazz c, Analyzer analyzer, EnumSet<Options> options,
			XMLAttributeFinder finder, Version minVersion) throws Exception {
		AnnotationReader r = new AnnotationReader(analyzer, c, options, finder, minVersion);
		return r.getDef();
	}

	private ComponentDef getDef() throws Exception {
		clazz.parseClassFileWithCollector(this);
		if (component.implementation == null)
			return null;

		if (options.contains(Options.inherit)) {
			baseclass = false;
			while (extendsClass != null) {
				if (extendsClass.isJava())
					break;

				Clazz ec = analyzer.findClass(extendsClass);
				if (ec == null) {
					analyzer.error("Missing super class for DS annotations: %s from %s", extendsClass,
							clazz.getClassName())
							.details(new DeclarativeServicesAnnotationError(className.getFQN(), null, null,
									ErrorType.UNABLE_TO_LOCATE_SUPER_CLASS));
					break;
				} else {
					ec.parseClassFileWithCollector(this);
				}
			}
		}
		for (ReferenceDef rdef : component.references.values()) {
			if (rdef.bind != null) {
				rdef.unbind = referredMethod(analyzer, rdef, rdef.unbind, "add(.*)", "remove$1", "(.*)", "un$1");
				rdef.updated = referredMethod(analyzer, rdef, rdef.updated, "(add|set|bind)(.*)", "updated$2", "(.*)",
						"updated$1");

				if (rdef.policy == ReferencePolicy.DYNAMIC && rdef.unbind == null)
					analyzer.error("In component class %s, reference %s is dynamic but has no unbind method.",
							className.getFQN(), rdef.name)
							.details(getDetails(rdef, ErrorType.DYNAMIC_REFERENCE_WITHOUT_UNBIND));
			}
		}
		return component;
	}

	/**
	 * @param analyzer
	 * @param rdef
	 */
	protected String referredMethod(Analyzer analyzer, ReferenceDef rdef, String value, String... matches) {
		if (value == null) {
			String bind = rdef.bind;
			for (int i = 0; i < matches.length; i += 2) {
				Matcher m = Pattern.compile(matches[i]).matcher(bind);
				if (m.matches()) {
					value = m.replaceFirst(matches[i + 1]);
					break;
				}
			}
		} else if (value.equals("-"))
			return null;

		if (methods.containsKey(value)) {
			for (Clazz.MethodDef method : methods.get(value)) {
				String service = determineReferenceType(method.getDescriptor().toString(), rdef, rdef.service, null);
				if (service != null) {
					if (!method.isProtected())
						component.updateVersion(V1_1);
					return value;
				}
			}
			analyzer.warning(
					"None of the methods related to '%s' in the class '%s' named '%s' for service type '%s' have an acceptable signature. The descriptors found are:",
					rdef.bind, component.implementation, value, rdef.service);
			// We make this a separate loop because we shouldn't add warnings
			// until we know that there was no match
			// We need to include the method name in the warning or it may be
			// ignored as duplicate (from another non-match)
			for (Clazz.MethodDef method : methods.get(value)) {
				analyzer.warning("  methodname: %s descriptor: %s", value, method.getDescriptor().toString())
						.details(getDetails(rdef, ErrorType.UNSET_OR_MODIFY_WITH_WRONG_SIGNATURE));
			}
		}
		return null;
	}

	@Override
	public void classEnd() throws Exception {
		member = null;
	}

	@Override
	public void memberEnd() {
		member = null;
	}

	@Override
	public void annotation(Annotation annotation) {
		try {
			java.lang.annotation.Annotation a = annotation.getAnnotation();
			if (a instanceof Component)
				doComponent((Component) a, annotation);
			else if (a instanceof Activate)
				doActivate();
			else if (a instanceof Deactivate)
				doDeactivate();
			else if (a instanceof Modified)
				doModified();
			else if (a instanceof Reference)
				doReference((Reference) a, annotation);
			else if (a instanceof Designate)
				doDesignate((Designate) a);
			else if (annotation.getName().getFQN().startsWith("aQute.bnd.annotation.component"))
				handleMixedUsageError(annotation);
			else {
				XMLAttribute xmlAttr = finder.getXMLAttribute(annotation);
				if (xmlAttr != null) {
					doXmlAttribute(annotation, xmlAttr);
				}
			}
		} catch (Exception e) {
			analyzer.exception(e, "During generation of a component on class %s, exception %s", clazz, e);
		}
	}

	private void handleMixedUsageError(Annotation annotation) throws Exception {
		DeclarativeServicesAnnotationError errorDetails;

		String fqn = annotation.getName().getFQN();

		switch (annotation.getElementType()) {
			case METHOD :
				errorDetails = new DeclarativeServicesAnnotationError(className.getFQN(), member.getName(),
						member.getDescriptor().toString(), ErrorType.MIXED_USE_OF_DS_ANNOTATIONS_STD);
				break;
			case FIELD :
				errorDetails = new DeclarativeServicesAnnotationError(className.getFQN(), member.getName(),
						ErrorType.MIXED_USE_OF_DS_ANNOTATIONS_STD);
				break;
			default :
				errorDetails = new DeclarativeServicesAnnotationError(className.getFQN(), null,
						ErrorType.MIXED_USE_OF_DS_ANNOTATIONS_STD);
		}
		List<DeclarativeServicesAnnotationError> errors = mismatchedAnnotations.get(fqn);
		if (errors == null) {
			errors = new ArrayList<DeclarativeServicesAnnotationError>();
			mismatchedAnnotations.put(fqn, errors);
		}
		errors.add(errorDetails);
	}

	private void doXmlAttribute(Annotation annotation, XMLAttribute xmlAttr) {
		// make sure doc is namespace aware, since we are adding namespaced
		// attributes.
		component.updateVersion(V1_1);
		if (member == null)
			component.addExtensionAttribute(xmlAttr, annotation);
		else {
			ReferenceDef ref = referencesByMember.get(member);
			if (ref == null) {
				ref = new ReferenceDef(finder);
				referencesByMember.put(member, ref);
			}
			ref.addExtensionAttribute(xmlAttr, annotation);
		}
	}

	protected void doDesignate(Designate a) {
		if (a.factory() && component.configurationPolicy == null)
			component.configurationPolicy = ConfigurationPolicy.REQUIRE;
	}

	/**
	 * 
	 */
	protected void doActivate() {
		String methodDescriptor = member.getDescriptor().toString();
		DeclarativeServicesAnnotationError details = new DeclarativeServicesAnnotationError(className.getFQN(),
				member.getName(), methodDescriptor, ErrorType.ACTIVATE_SIGNATURE_ERROR);
		if (!(member instanceof MethodDef)) {
			analyzer.error("Activate annotation on a field %s.%s", clazz, member.getDescriptor()).details(details);
			return;
		}
		boolean hasMapReturnType = false;
		Matcher m = LIFECYCLEDESCRIPTORDS10.matcher(methodDescriptor);
		if ("activate".equals(member.getName()) && m.matches()) {
			component.activate = member.getName();
			hasMapReturnType = m.group(3) != null;
			if (!member.isProtected())
				component.updateVersion(V1_1);
		} else {
			m = LIFECYCLEDESCRIPTORDS11.matcher(methodDescriptor);
			if (m.matches()) {
				component.activate = member.getName();
				component.updateVersion(V1_1);
				hasMapReturnType = m.group(6) != null;
			} else {
				m = LIFECYCLEDESCRIPTORDS13.matcher(methodDescriptor);
				if (m.matches()) {
					component.activate = member.getName();
					component.updateVersion(V1_3);
					hasMapReturnType = m.group(4) != null;
					processAnnotationArguments(methodDescriptor, details);
				} else
					analyzer.error("Activate method for %s descriptor %s is not acceptable.", clazz,
							member.getDescriptor()).details(details);
			}
		}
		checkMapReturnType(hasMapReturnType, details);

	}

	/**
	 * 
	 */
	protected void doDeactivate() {
		String methodDescriptor = member.getDescriptor().toString();
		DeclarativeServicesAnnotationError details = new DeclarativeServicesAnnotationError(className.getFQN(),
				member.getName(), methodDescriptor, ErrorType.DEACTIVATE_SIGNATURE_ERROR);

		if (!(member instanceof MethodDef)) {
			analyzer.error("Deactivate annotation on a field %s.%s", clazz, member.getDescriptor()).details(details);
			return;
		}
		boolean hasMapReturnType = false;
		Matcher m = LIFECYCLEDESCRIPTORDS10.matcher(methodDescriptor);
		if ("deactivate".equals(member.getName()) && m.matches()) {
			component.deactivate = member.getName();
			hasMapReturnType = m.group(3) != null;
			if (!member.isProtected())
				component.updateVersion(V1_1);
		} else {
			m = DEACTIVATEDESCRIPTORDS11.matcher(methodDescriptor);
			if (m.matches()) {
				component.deactivate = member.getName();
				component.updateVersion(V1_1);
				hasMapReturnType = m.group(8) != null;
			} else {
				m = DEACTIVATEDESCRIPTORDS13.matcher(methodDescriptor);
				if (m.matches()) {
					component.deactivate = member.getName();
					component.updateVersion(V1_3);
					hasMapReturnType = m.group(6) != null;
					processAnnotationArguments(methodDescriptor, details);
				} else
					analyzer.error("Deactivate method for %s descriptor %s is not acceptable.", clazz,
							member.getDescriptor()).details(details);
			}
		}
		checkMapReturnType(hasMapReturnType, details);
	}

	/**
	 * 
	 */
	protected void doModified() {
		String methodDescriptor = member.getDescriptor().toString();
		DeclarativeServicesAnnotationError details = new DeclarativeServicesAnnotationError(className.getFQN(),
				member.getName(), methodDescriptor, ErrorType.MODIFIED_SIGNATURE_ERROR);

		if (!(member instanceof MethodDef)) {
			analyzer.error("Modified annotation on a field %s.%s", clazz, member.getDescriptor()).details(details);
			return;
		}
		boolean hasMapReturnType = false;
		Matcher m = LIFECYCLEDESCRIPTORDS11.matcher(methodDescriptor);
		if (m.matches()) {
			component.modified = member.getName();
			component.updateVersion(V1_1);
			hasMapReturnType = m.group(6) != null;
		} else {
			m = LIFECYCLEDESCRIPTORDS13.matcher(methodDescriptor);
			if (m.matches()) {
				component.modified = member.getName();
				component.updateVersion(V1_3);
				hasMapReturnType = m.group(4) != null;
				processAnnotationArguments(methodDescriptor, details);
			} else

				analyzer.error("Modified method for %s descriptor %s is not acceptable.", clazz, member.getDescriptor())
						.details(details);
		}
		checkMapReturnType(hasMapReturnType, details);
	}

	/**
	 * look for annotation arguments and extract properties from them
	 * 
	 * @param methodDescriptor
	 * @param fqn TODO
	 * @param method TODO
	 * @param descriptor TODO
	 */
	private void processAnnotationArguments(final String methodDescriptor,
			final DeclarativeServicesAnnotationError details) {
		Matcher m = LIFECYCLEARGUMENT.matcher(methodDescriptor);

		while (m.find()) {
			String type = m.group(6);
			if (type != null) {
				TypeRef typeRef = analyzer.getTypeRef(type);
				try {
					Clazz clazz = analyzer.findClass(typeRef);
					if (clazz.isAnnotation()) {
						clazz.parseClassFileWithCollector(new ComponentPropertyTypeDataCollector(methodDescriptor, details));
					} else if (clazz.isInterface() && options.contains(Options.felixExtensions)) {
						// ok
					} else {
						analyzer.error("Non annotation argument to lifecycle method with descriptor %s,  type %s",
								methodDescriptor, type).details(details);
					}
				} catch (Exception e) {
					analyzer.exception(e,
							"Exception looking at annotation argument to lifecycle method with descriptor %s,  type %s",
							methodDescriptor, type).details(details);
				}
			}
		}
	}

	private final class ComponentPropertyTypeDataCollector extends ClassDataCollector {
		private final String								methodDescriptor;
		private final DeclarativeServicesAnnotationError	details;
		private final MultiMap<String,String>				props			= new MultiMap<String,String>();
		private final Map<String,String>					propertyTypes	= new HashMap<>();
		private int											hasNoDefault	= 0;
		private boolean										hasValue		= false;
		private FieldDef									prefixField		= null;
		private TypeRef										typeRef			= null;
	
		private ComponentPropertyTypeDataCollector(String methodDescriptor,
				DeclarativeServicesAnnotationError details) {
			this.methodDescriptor = methodDescriptor;
			this.details = details;
		}
	
		@Override
		public void classBegin(int access, TypeRef name) {
			typeRef = name;
		}

		@Override
		public void field(FieldDef defined) {
			if (defined.isStatic() && defined.getName().equals("PREFIX_")) {
				prefixField = defined;
			}
		}
	
		@Override
		public void method(MethodDef defined) {
			if (defined.isStatic()) {
				return;
			}
			if (defined.getName().equals("value")) {
				hasValue = true;
			} else {
				hasNoDefault++;
			}
		}
	
		@Override
		public void annotationDefault(MethodDef defined, Object value) {
			if (!defined.getName().equals("value")) {
				hasNoDefault--;
			}
			// check type, exit with warning if annotation
			// or annotation array
			boolean isClass = false;
			Class< ? > typeClass = null;
			TypeRef type = defined.getType().getClassRef();
			if (!type.isPrimitive()) {
				if (type == analyzer.getTypeRef("java/lang/Class")) {
					isClass = true;
				} else {
					try {
						Clazz r = analyzer.findClass(type);
						if (r.isAnnotation()) {
							analyzer.warning("Nested annotation type found in member %s, %s",
									defined.getName(), type.getFQN()).details(details);
							return;
						}
					} catch (Exception e) {
						analyzer.exception(e,
								"Exception looking at annotation type to lifecycle method with descriptor %s,  type %s",
								methodDescriptor, type).details(details);
					}
				}
			} else {
				typeClass = wrappers.get(type.getFQN());
			}
			if (value != null) {
				String name = defined.getName();
				if (value.getClass().isArray()) {
					// add element individually
					int len = Array.getLength(value);
					for (int i = 0; i < len; i++) {
						Object element = Array.get(value, i);
						valueToProperty(name, element, isClass, typeClass);
					}
					if (len == 1) {
						// To make sure the output is an array, we must make
						// sure there is more than one entry
						props.add(name, ComponentDef.MARKER);
					}
				} else {
					valueToProperty(name, value, isClass, typeClass);
				}
			}
		}
	
		@Override
		public void classEnd() throws Exception {
			String prefix = null;
			if (prefixField != null) {
				Object c = prefixField.getConstant();
				if (prefixField.isFinal() && (prefixField.getType() == analyzer.getTypeRef("java/lang/String"))
						&& (c instanceof String)) {
					prefix = (String) c;
					component.updateVersion(V1_4);
				} else {
					analyzer.warning(
							"Field PREFIX_ in %s is not a static final String field with a compile-time constant value: %s",
							typeRef.getFQN(),
							c).details(details);
				}
			}
			String singleElementAnnotation = null;
			if (hasValue && (hasNoDefault == 0)) {
				StringBuilder sb = new StringBuilder(typeRef.getShorterName());
				boolean lastLowerCase = false;
				for (int i = 0; i < sb.length(); i++) {
					char c = sb.charAt(i);
					if (Character.isUpperCase(c)) {
						sb.setCharAt(i, Character.toLowerCase(c));
						if (lastLowerCase) {
							sb.insert(i++, '.');
						}
						lastLowerCase = false;
					} else {
						lastLowerCase = Character.isLowerCase(c);
					}
				}
				singleElementAnnotation = sb.toString();
				component.updateVersion(V1_4);
			}
			for (Entry<String,List<String>> entry : props.entrySet()) {
				String key = entry.getKey();
				List<String> value = entry.getValue();
				String type = propertyTypes.get(key);
				if ((singleElementAnnotation != null) && key.equals("value")) {
					key = singleElementAnnotation;
				} else {
					key = identifierToPropertyName(key);
				}
				if (prefix != null) {
					key = prefix + key;
				}
				component.property.put(key, value);
				component.propertyType.put(key, type);
			}
		}

		private void valueToProperty(String name, Object value, boolean isClass,
				Class< ? > typeClass) {
			if (isClass)
				value = ((TypeRef) value).getFQN();
			if (typeClass == null)
				typeClass = value.getClass();
			// enums already come out as the enum name, no
			// processing needed.
			String type = typeClass.getSimpleName();
			propertyTypes.put(name, type);
			props.add(name, value.toString());
		}
	
		private String identifierToPropertyName(String name) {
			Matcher m = IDENTIFIERTOPROPERTY.matcher(name);
			StringBuffer b = new StringBuffer();
			while (m.find()) {
				String replace;
				if (m.group(1) != null) // __ to _
					replace = "_";
				else if (m.group(2) != null) // _ to .
					replace = ".";
				else if (m.group(3) != null) { // $_$ to -
					replace = "-";
					component.updateVersion(V1_4);
				}
				else if (m.group(4) != null) // $$ to $
					replace = "\\$";
				else // $ removed.
					replace = "";
				m.appendReplacement(b, replace);
			}
			m.appendTail(b);
			return b.toString();
		}
	}

	/**
	 * @param reference @Reference proxy backed by raw.
	 * @param raw @Reference contents
	 * @throws Exception
	 */
	protected void doReference(Reference reference, Annotation raw) throws Exception {
		ReferenceDef def;
		if (member == null)
			def = new ReferenceDef(finder);
		else if (referencesByMember.containsKey(member))
			def = referencesByMember.get(member);
		else {
			def = new ReferenceDef(finder);
			referencesByMember.put(member, def);
		}
		def.className = className.getFQN();
		def.name = reference.name();
		def.bind = reference.bind();
		def.unbind = reference.unbind();
		def.updated = reference.updated();
		def.field = reference.field();
		def.fieldOption = reference.fieldOption();
		def.cardinality = reference.cardinality();
		def.policy = reference.policy();
		def.policyOption = reference.policyOption();
		def.scope = reference.scope();

		// Check if we have a target, this must be a filter
		def.target = reference.target();

		DeclarativeServicesAnnotationError details = getDetails(def, ErrorType.REFERENCE);

		if (def.target != null) {
			String error = Verifier.validateFilter(def.target);
			if (error != null)
				analyzer.error("Invalid target filter %s for %s: %s", def.target, def.name, error)
						.details(getDetails(def, ErrorType.INVALID_TARGET_FILTER));
		}

		String annoService = null;
		TypeRef annoServiceTR = raw.get("service");
		if (annoServiceTR != null)
			annoService = annoServiceTR.getFQN();

		if (member != null) {
			if (member instanceof MethodDef) {
				def.bindDescriptor = member.getDescriptor().toString();
				if (!member.isProtected())
					def.updateVersion(V1_1);
				def.bind = member.getName();
				if (def.name == null) {
					Matcher m = BINDNAME.matcher(member.getName());
					if (m.matches())
						def.name = m.group(2);
					else
						analyzer.error("Invalid name for bind method %s", member.getName())
								.details(getDetails(def, ErrorType.INVALID_REFERENCE_BIND_METHOD_NAME));
				}

				def.service = determineReferenceType(def.bindDescriptor, def, annoService, member.getSignature());

				if (def.service == null)
					analyzer.error("In component %s, method %s,  cannot recognize the signature of the descriptor: %s",
							component.effectiveName(), def.name, member.getDescriptor());

			} else if (member instanceof FieldDef) {
				def.updateVersion(V1_3);
				def.field = member.getName();
				if (def.name == null)
					def.name = def.field;
				if (def.policy == null && member.isVolatile())
					def.policy = ReferencePolicy.DYNAMIC;

				String sig = member.getSignature();
				if (sig == null)
					// no generics, the descriptor will be the class name.
					sig = member.getDescriptor().toString();
				String[] sigs = sig.split("[<;>]");
				int sigLength = sigs.length;
				int index = 0;
				boolean isCollection = false;
				if ("Ljava/util/Collection".equals(sigs[index]) || "Ljava/util/List".equals(sigs[index])) {
					index++;
					isCollection = true;
				}
				// Along with determining the FieldCollectionType, the following
				// code positions index to read the service type.
				FieldCollectionType fieldCollectionType = null;
				if (sufficientGenerics(index, sigLength, def, sig)) {
					if ("Lorg/osgi/framework/ServiceReference".equals(sigs[index])) {
						if (sufficientGenerics(index++, sigLength, def, sig)) {
							fieldCollectionType = FieldCollectionType.reference;
						}
					} else if ("Lorg/osgi/service/component/ComponentServiceObjects".equals(sigs[index])) {
						if (sufficientGenerics(index++, sigLength, def, sig)) {
							fieldCollectionType = FieldCollectionType.serviceobjects;
						}
					} else if ("Ljava/util/Map".equals(sigs[index])) {
						if (sufficientGenerics(index++, sigLength, def, sig)) {
							fieldCollectionType = FieldCollectionType.properties;
						}
					} else if ("Ljava/util/Map$Entry".equals(sigs[index])
							&& sufficientGenerics(index++ + 5, sigLength, def, sig)) {
						if ("Ljava/util/Map".equals(sigs[index++]) && "Ljava/lang/String".equals(sigs[index++])) {
							if ("Ljava/lang/Object".equals(sigs[index]) || "+Ljava/lang/Object".equals(sigs[index])) {
								fieldCollectionType = FieldCollectionType.tuple;
								index += 3; // ;>;
							} else if ("*".equals(sigs[index])) {
								fieldCollectionType = FieldCollectionType.tuple;
								index += 2; // >;
							} else {
								index = sigLength;// no idea what service might
													// be.
							}
						}
					} else {
						fieldCollectionType = FieldCollectionType.service;
					}
				}
				if (isCollection) {
					if (def.cardinality == null)
						def.cardinality = ReferenceCardinality.MULTIPLE;
					def.fieldCollectionType = fieldCollectionType;
				}
				if (def.policy == ReferencePolicy.DYNAMIC && (def.cardinality == ReferenceCardinality.MULTIPLE
						|| def.cardinality == ReferenceCardinality.AT_LEAST_ONE) && member.isFinal()) {
					if (def.fieldOption == FieldOption.REPLACE)
						analyzer.error(
								"In component %s, collection type field: %s is final and dynamic but marked with 'replace' fieldOption. Changing this to 'update'.",
								className, def.field)
								.details(getDetails(def, ErrorType.DYNAMIC_FINAL_FIELD_WITH_REPLACE));
					def.fieldOption = FieldOption.UPDATE;
				}
				if (annoService == null && index < sigs.length) {
					annoService = sigs[index].substring(1).replace('/', '.');
				}
				def.service = annoService;
				if (def.service == null)
					analyzer.error("In component %s, method %s,  cannot recognize the signature of the descriptor: %s",
							component.effectiveName(), def.name, member.getDescriptor()).details(details);

			} // end field
		} else {// not a member
			def.service = annoService;
			if (def.name == null) {
				analyzer.error(
						"Name must be supplied for a @Reference specified in the @Component annotation. Service: %s",
						def.service).details(getDetails(def, ErrorType.MISSING_REFERENCE_NAME));
				return;
			}
		}

		if (component.references.containsKey(def.name))
			analyzer.error(
					"In component %s, multiple references with the same name: %s. Previous def: %s, this def: %s",
					className, component.references.get(def.name), def.service, "")
					.details(getDetails(def, ErrorType.MULTIPLE_REFERENCES_SAME_NAME));
		else
			component.references.put(def.name, def);

	}

	private DeclarativeServicesAnnotationError getDetails(ReferenceDef def, ErrorType type) {
		if (def == null)
			return null;

		return new DeclarativeServicesAnnotationError(className.getFQN(), def.bind, def.bindDescriptor, type);
	}

	private boolean sufficientGenerics(int index, int sigLength, ReferenceDef def, String sig) {
		if (index + 1 > sigLength) {
			analyzer.error(
					"In component %s, method %s,  signature: %s does not have sufficient generic type information",
					component.effectiveName(), def.name, sig);
			return false;
		}
		return true;
	}

	private String determineReferenceType(String methodDescriptor, ReferenceDef def, String annoService,
			String signature) {
		String inferredService = null;
		String plainType = null;
		boolean hasMapReturnType;
		Version minVersion = null;

		DeclarativeServicesAnnotationError details = getDetails(def, ErrorType.REFERENCE);

		// We have to find the type of the current method to
		// link it to the referenced service.
		Matcher m = BINDDESCRIPTORDS10.matcher(methodDescriptor);
		if (m.matches()) {
			inferredService = Descriptors.binaryToFQN(m.group(1));
			// ServiceReference (group 3) is always OK, match is always OK
			if (m.group(3) == null && noMatch(annoService, inferredService)) {
				if (m.group(7) == null) {
					// single arg of recognized Map, Map.Entry or ServiceObjects
					// so we must be V3.
					minVersion = V1_3;
				}
			}
			if (m.group(3) != null) {
				plainType = "Lorg/osgi/framework/ServiceReference<";
				inferredService = null;
			} else if (m.group(4) != null) {
				plainType = "Lorg/osgi/service/component/ComponentServiceObjects<";
				inferredService = null;
			} else if (m.group(5) != null) {
				plainType = "Ljava/util/Map$Entry<Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;";
				inferredService = null;
			} else if (m.group(6) != null) {
				// we cannot infer the service from just a map.
				inferredService = null;
			}
			hasMapReturnType = m.group(9) != null;
		} else {
			m = BINDDESCRIPTORDS11.matcher(methodDescriptor);
			if (m.matches()) {
				inferredService = Descriptors.binaryToFQN(m.group(1));
				minVersion = V1_1;
				hasMapReturnType = m.group(4) != null;
			} else {
				m = BINDDESCRIPTORDS13.matcher(methodDescriptor);
				if (m.matches()) {
					inferredService = m.group(7);
					if (inferredService != null)
						inferredService = Descriptors.binaryToFQN(inferredService);
					minVersion = V1_3;
					if (ReferenceScope.PROTOTYPE != def.scope && m.group(3) != null) {
						analyzer.error("In component %s, to use ComponentServiceObjects the scope must be 'prototype'",
								component.implementation, "").details(details);
					}
					if (annoService == null)
						if (m.group(2) != null)
							plainType = "Lorg/osgi/framework/ServiceReference<";
						else if (m.group(3) != null)
							plainType = "Lorg/osgi/service/component/ComponentServiceObjects<";
						else if (m.group(5) != null)
							plainType = "Ljava/util/Map$Entry<Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;";

					hasMapReturnType = m.group(9) != null;
				} else {
					return null;
				}
			}
		}

		String service = annoService;
		if (inferredService == null && signature != null && plainType != null) {
			int start = signature.indexOf(plainType);
			if (start > -1) {
				start += plainType.length();
				String[] sigs = signature.substring(start).split("[<;>]");
				if (sigs.length > 0) {
					String sig = sigs[0];
					if (sig.startsWith("-")) {
						inferredService = Object.class.getName();
					} else {
						int index = sig.startsWith("+") ? 2 : 1;
						inferredService = sig.substring(index).replace('/', '.');
					}
				}
			}
		}
		// if the type is specified it may still not match as it could
		// be a superclass of the specified service.
		if (!analyzer.assignable(annoService, inferredService)) {
			return null;
		}
		if (service == null)
			service = inferredService;
		checkMapReturnType(hasMapReturnType, details);
		if (minVersion != null)
			def.updateVersion(minVersion);
		return service;
	}

	private void checkMapReturnType(boolean hasMapReturnType, DeclarativeServicesAnnotationError details) {
		if (hasMapReturnType) {
			if (!options.contains(Options.felixExtensions)) {
				analyzer.error(
						"In component %s, to use a return type of Map you must specify the -dsannotations-options felixExtensions flag "
								+ " and use a felix extension attribute or explicitly specify the appropriate xmlns.",
						component.implementation, "").details(details);
			}
		}
	}

	/**
	 * @param annoService
	 * @param inferredService
	 * @return true if the inferred service is a non-parameter object because it
	 *         differs from the specified service type.
	 */
	private boolean noMatch(String annoService, String inferredService) {
		if (annoService == null)
			return false;
		return !annoService.equals(inferredService);
	}

	/**
	 * @param annotation
	 * @throws Exception
	 */
	@SuppressWarnings("deprecation")
	protected void doComponent(Component comp, Annotation annotation) throws Exception {

		if (!mismatchedAnnotations.isEmpty()) {
			String componentName = comp.name();
			componentName = (componentName == null) ? className.getFQN() : componentName;
			for (Entry<String,List<DeclarativeServicesAnnotationError>> e : mismatchedAnnotations.entrySet()) {
				for (DeclarativeServicesAnnotationError errorDetails : e.getValue()) {
					if (errorDetails.fieldName != null) {
						analyzer.error(
								"The DS component %s uses standard annotations to declare it as a component, but also uses the bnd DS annotation: %s on field %s. It is an error to mix these two types of annotations",
								componentName, e.getKey(), errorDetails.fieldName).details(errorDetails);
					} else if (errorDetails.methodName != null) {
						analyzer.error(
								"The DS component %s uses standard annotations to declare it as a component, but also uses the bnd DS annotation: %s on method %s with signature %s. It is an error to mix these two types of annotations",
								componentName, e.getKey(), errorDetails.methodName, errorDetails.methodSignature)
								.details(errorDetails);
					} else {
						analyzer.error(
								"The DS component %s uses standard annotations to declare it as a component, but also uses the bnd DS annotation: %s. It is an error to mix these two types of annotations",
								componentName, e.getKey()).details(errorDetails);
					}
				}
			}
			return;
		}

		// Check if we are doing a super class
		if (component.implementation != null)
			return;

		component.implementation = clazz.getClassName();
		component.name = comp.name();
		component.factory = comp.factory();
		component.configurationPolicy = comp.configurationPolicy();
		if (annotation.get("enabled") != null)
			component.enabled = comp.enabled();
		if (annotation.get("factory") != null)
			component.factory = comp.factory();
		if (annotation.get("immediate") != null)
			component.immediate = comp.immediate();
		if (annotation.get("servicefactory") != null)
			component.scope = comp.servicefactory() ? ServiceScope.BUNDLE : ServiceScope.SINGLETON;
		if (annotation.get("scope") != null && comp.scope() != ServiceScope.DEFAULT) {
			component.scope = comp.scope();
			if (comp.scope() == ServiceScope.PROTOTYPE) {
				component.updateVersion(V1_3);
			}
		}

		if (annotation.get("configurationPid") != null) {
			component.configurationPid = comp.configurationPid();
			if (component.configurationPid.length > 1) {
				component.updateVersion(V1_3);
			} else {
				component.updateVersion(V1_2);
			}
		}

		if (annotation.get("xmlns") != null)
			component.xmlns = comp.xmlns();

		String properties[] = comp.properties();
		if (properties != null)
			for (String entry : properties) {
				if (entry.contains("=")) {
					analyzer.error(
							"Found an = sign in an OSGi DS Component annotation on %s. In the bnd annotation "
									+ "this is an actual property but in the OSGi, this element must refer to a path with Java properties. "
									+ "However, found a path with an '=' sign which looks like a mixup (%s) with the 'property' element.",
							clazz, entry)
							.details(new DeclarativeServicesAnnotationError(className.getFQN(), null, null,
									ErrorType.COMPONENT_PROPERTIES_ERROR));
				}
				component.properties.add(entry);
			}

		doProperty(comp.property());
		Object[] x = annotation.get("service");

		if (x == null) {
			// Use the found interfaces, but convert from internal to
			// fqn.
			if (interfaces != null) {
				List<TypeRef> result = new ArrayList<TypeRef>();
				for (int i = 0; i < interfaces.length; i++) {
					if (!interfaces[i].equals(analyzer.getTypeRef("scala/ScalaObject")))
						result.add(interfaces[i]);
				}
				component.service = result.toArray(EMPTY);
			}
		} else {
			// We have explicit interfaces set
			component.service = new TypeRef[x.length];
			for (int i = 0; i < x.length; i++) {
				TypeRef typeRef = (TypeRef) x[i];
				Clazz service = analyzer.findClass(typeRef);
				if (!analyzer.assignable(clazz, service)) {
					analyzer.error("Class %s is not assignable to specified service %s", clazz.getFQN(),
							typeRef.getFQN())
							.details(new DeclarativeServicesAnnotationError(className.getFQN(), null, null,
									ErrorType.INCOMPATIBLE_SERVICE));
				}
				component.service[i] = typeRef;
			}
		}

		// make sure reference processing knows this is a Reference in Component
		member = null;
		Object[] refAnnotations = annotation.get("reference");
		if (refAnnotations != null) {
			for (Object o : refAnnotations) {
				Annotation refAnnotation = (Annotation) o;
				Reference ref = refAnnotation.getAnnotation();
				doReference(ref, refAnnotation);
			}
		}

	}

	/**
	 * Parse the properties
	 */

	private void doProperty(String[] properties) {
		if (properties != null && properties.length > 0) {
			MultiMap<String,String> props = new MultiMap<String,String>();
			for (String p : properties) {
				Matcher m = PROPERTY_PATTERN.matcher(p);

				if (m.matches()) {
					String key = m.group(1);
					String type = m.group(2);
					if (type == null)
						type = "String";

					component.propertyType.put(key, type);

					String value = m.group(3);
					props.add(key, value);
				} else
					analyzer.error("Malformed property '%s' on component: %s", p, className);
			}
			component.property.putAll(props);
		}
	}

	/**
	 * Are called during class parsing
	 */

	@Override
	public void classBegin(int access, TypeRef name) {
		className = name;
	}

	@Override
	public void implementsInterfaces(TypeRef[] interfaces) {
		this.interfaces = interfaces;
	}

	@Override
	public void method(Clazz.MethodDef method) {
		if (method.isAbstract() || method.isStatic() || method.isBridge())
			return;

		if (!baseclass && method.isPrivate())
			return;

		this.member = method;
		methods.add(method.getName(), method);
	}

	@Override
	public void field(FieldDef field) {
		this.member = field;
	}

	@Override
	public void extendsClass(TypeRef name) {
		this.extendsClass = name;
	}

}
