org.eclipse.pde.ds.internal.annotations.AnnotationProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.pde.ds.internal.annotations.AnnotationProcessor.java

Source

/*******************************************************************************
 * Copyright (c) 2012, 2016 Ecliptical Software Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Ecliptical Software Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.pde.ds.internal.annotations;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Pattern;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.compiler.BuildContext;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.IModelChangedEvent;
import org.eclipse.pde.core.ModelChangedEvent;
import org.eclipse.pde.internal.core.project.PDEProject;
import org.eclipse.pde.internal.core.text.IDocumentAttributeNode;
import org.eclipse.pde.internal.core.text.IDocumentElementNode;
import org.eclipse.pde.internal.core.text.IDocumentObject;
import org.eclipse.pde.internal.core.text.IDocumentTextNode;
import org.eclipse.pde.internal.core.text.IModelTextChangeListener;
import org.eclipse.pde.internal.ds.core.IDSComponent;
import org.eclipse.pde.internal.ds.core.IDSConstants;
import org.eclipse.pde.internal.ds.core.IDSDocumentFactory;
import org.eclipse.pde.internal.ds.core.IDSImplementation;
import org.eclipse.pde.internal.ds.core.IDSModel;
import org.eclipse.pde.internal.ds.core.IDSObject;
import org.eclipse.pde.internal.ds.core.IDSProperties;
import org.eclipse.pde.internal.ds.core.IDSProperty;
import org.eclipse.pde.internal.ds.core.IDSProvide;
import org.eclipse.pde.internal.ds.core.IDSReference;
import org.eclipse.pde.internal.ds.core.IDSService;
import org.eclipse.pde.internal.ds.core.text.DSModel;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
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.ReferencePolicyOption;

@SuppressWarnings("restriction")
public class AnnotationProcessor extends ASTRequestor {

    private static final String DS_BUILDER = "org.eclipse.pde.ds.core.builder"; //$NON-NLS-1$

    static final Debug debug = Debug.getDebug("ds-annotation-builder/processor"); //$NON-NLS-1$

    private final ProjectContext context;

    private final Map<ICompilationUnit, BuildContext> fileMap;

    private boolean hasBuilder;

    public AnnotationProcessor(ProjectContext context, Map<ICompilationUnit, BuildContext> fileMap) {
        this.context = context;
        this.fileMap = fileMap;
    }

    @Override
    public void acceptAST(ICompilationUnit source, CompilationUnit ast) {
        // determine CU key
        String cuKey;
        IJavaElement parent = source.getParent();
        if (parent == null)
            cuKey = source.getElementName();
        else
            cuKey = String.format("%s/%s", parent.getElementName().replace('.', '/'), source.getElementName()); //$NON-NLS-1$

        context.getUnprocessed().remove(cuKey);

        ProjectState state = context.getState();
        HashMap<String, String> dsKeys = new HashMap<>();
        HashSet<DSAnnotationProblem> problems = new HashSet<>();

        ast.accept(new AnnotationVisitor(this, state, dsKeys, problems));

        // track abandoned files (may be garbage)
        Collection<String> oldDSKeys = state.updateMappings(cuKey, dsKeys);
        if (oldDSKeys != null) {
            oldDSKeys.removeAll(dsKeys.values());
            context.getAbandoned().addAll(oldDSKeys);
        }

        if (!problems.isEmpty()) {
            char[] filename = source.getResource().getFullPath().toString().toCharArray();
            for (DSAnnotationProblem problem : problems) {
                problem.setOriginatingFileName(filename);
                if (problem.getSourceStart() >= 0)
                    problem.setSourceLineNumber(ast.getLineNumber(problem.getSourceStart()));
            }

            BuildContext buildContext = fileMap.get(source);
            if (buildContext != null)
                buildContext.recordNewProblems(problems.toArray(new CategorizedProblem[problems.size()]));
        }
    }

    private void ensureDSProject(IProject project) throws CoreException {
        IProjectDescription description = project.getDescription();
        ICommand[] commands = description.getBuildSpec();

        for (ICommand command : commands) {
            if (DS_BUILDER.equals(command.getBuilderName()))
                return;
        }

        ICommand[] newCommands = new ICommand[commands.length + 1];
        System.arraycopy(commands, 0, newCommands, 0, commands.length);
        ICommand command = description.newCommand();
        command.setBuilderName(DS_BUILDER);
        newCommands[newCommands.length - 1] = command;
        description.setBuildSpec(newCommands);
        project.setDescription(description, null);
    }

    private void ensureExists(IFolder folder) throws CoreException {
        if (folder.exists())
            return;

        IContainer parent = folder.getParent();
        if (parent != null && parent.getType() == IResource.FOLDER)
            ensureExists((IFolder) parent);

        folder.create(true, true, null);
    }

    void verifyOutputLocation(IFile file) throws CoreException {
        if (hasBuilder)
            return;

        hasBuilder = true;
        IProject project = file.getProject();

        IPath parentPath = file.getParent().getProjectRelativePath();
        if (!parentPath.isEmpty()) {
            IFolder folder = project.getFolder(parentPath);
            ensureExists(folder);
        }

        try {
            ensureDSProject(project);
        } catch (CoreException e) {
            Activator.log(e);
        }
    }
}

@SuppressWarnings("restriction")
class AnnotationVisitor extends ASTVisitor {

    private static final String COMPONENT_ANNOTATION = DSAnnotationCompilationParticipant.COMPONENT_ANNOTATION;

    private static final String ACTIVATE_ANNOTATION = Activate.class.getName();

    private static final String MODIFIED_ANNOTATION = Modified.class.getName();

    private static final String DEACTIVATE_ANNOTATION = Deactivate.class.getName();

    private static final String REFERENCE_ANNOTATION = Reference.class.getName();

    private static final Pattern PID_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*"); //$NON-NLS-1$

    private static final String NAMESPACE_1_1 = IDSConstants.NAMESPACE;

    private static final String NAMESPACE_1_2 = "http://www.osgi.org/xmlns/scr/v1.2.0"; //$NON-NLS-1$

    private static final String ATTRIBUTE_COMPONENT_CONFIGURATION_PID = "configuration-pid"; //$NON-NLS-1$

    private static final String ATTRIBUTE_REFERENCE_POLICY_OPTION = "policy-option"; //$NON-NLS-1$

    private static final String ATTRIBUTE_REFERENCE_UPDATED = "updated"; //$NON-NLS-1$

    private static final String VALUE_REFERENCE_POLICY_OPTION_RELUCTANT = "reluctant"; //$NON-NLS-1$

    private static final Set<String> PROPERTY_TYPES = Collections
            .unmodifiableSet(new HashSet<>(Arrays.asList(null, IDSConstants.VALUE_PROPERTY_TYPE_STRING,
                    IDSConstants.VALUE_PROPERTY_TYPE_LONG, IDSConstants.VALUE_PROPERTY_TYPE_DOUBLE,
                    IDSConstants.VALUE_PROPERTY_TYPE_FLOAT, IDSConstants.VALUE_PROPERTY_TYPE_INTEGER,
                    IDSConstants.VALUE_PROPERTY_TYPE_BYTE, IDSConstants.VALUE_PROPERTY_TYPE_CHAR,
                    IDSConstants.VALUE_PROPERTY_TYPE_BOOLEAN, IDSConstants.VALUE_PROPERTY_TYPE_SHORT)));

    private static final Comparator<IDSReference> REF_NAME_COMPARATOR = new Comparator<IDSReference>() {

        @Override
        public int compare(IDSReference o1, IDSReference o2) {
            return o1.getReferenceName().compareTo(o2.getReferenceName());
        }
    };

    private static final Debug debug = AnnotationProcessor.debug;

    private final AnnotationProcessor processor;

    private final ProjectState state;

    private final ValidationErrorLevel errorLevel;

    private final ValidationErrorLevel missingUnbindMethodLevel;

    private final Map<String, String> dsKeys;

    private final Set<DSAnnotationProblem> problems;

    public AnnotationVisitor(AnnotationProcessor processor, ProjectState state, Map<String, String> dsKeys,
            Set<DSAnnotationProblem> problems) {
        this.processor = processor;
        this.state = state;
        this.errorLevel = state.getErrorLevel();
        this.missingUnbindMethodLevel = state.getMissingUnbindMethodLevel();
        this.dsKeys = dsKeys;
        this.problems = problems;
    }

    @Override
    public boolean visit(TypeDeclaration type) {
        if (!Modifier.isPublic(type.getModifiers())) {
            // non-public types cannot be (or have nested) components
            if (errorLevel.isIgnore())
                return false;

            Annotation annotation = findComponentAnnotation(type);
            if (annotation != null)
                reportProblem(annotation, null, problems,
                        NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_notPublic,
                                type.getName().getIdentifier()),
                        type.getName().getIdentifier());

            return true;
        }

        Annotation annotation = findComponentAnnotation(type);
        if (annotation != null) {
            boolean isInterface = false;
            boolean isAbstract = false;
            boolean isNested = false;
            boolean noDefaultConstructor = false;
            if ((isInterface = type.isInterface()) || (isAbstract = Modifier.isAbstract(type.getModifiers()))
                    || (isNested = (!type.isPackageMemberTypeDeclaration() && !isNestedPublicStatic(type)))
                    || (noDefaultConstructor = !hasDefaultConstructor(type))) {
                // interfaces, abstract types, non-static/non-public nested types, or types with no default constructor cannot be components
                if (errorLevel != ValidationErrorLevel.ignore) {
                    if (isInterface)
                        reportProblem(annotation, null, problems,
                                NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_interface,
                                        type.getName().getIdentifier()),
                                type.getName().getIdentifier());
                    else if (isAbstract)
                        reportProblem(annotation, null, problems,
                                NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_abstract,
                                        type.getName().getIdentifier()),
                                type.getName().getIdentifier());
                    else if (isNested)
                        reportProblem(annotation, null, problems,
                                NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_notTopLevel,
                                        type.getName().getIdentifier()),
                                type.getName().getIdentifier());
                    else if (noDefaultConstructor)
                        reportProblem(annotation, null, problems,
                                NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_noDefaultConstructor,
                                        type.getName().getIdentifier()),
                                type.getName().getIdentifier());
                    else
                        reportProblem(annotation, null, problems,
                                NLS.bind(Messages.AnnotationProcessor_invalidComponentImplementationClass,
                                        type.getName().getIdentifier()),
                                type.getName().getIdentifier());
                }
            } else {
                ITypeBinding typeBinding = type.resolveBinding();
                if (typeBinding == null) {
                    if (debug.isDebugging())
                        debug.trace(String.format("Unable to resolve binding for type: %s", type)); //$NON-NLS-1$
                } else {
                    IAnnotationBinding annotationBinding = annotation.resolveAnnotationBinding();
                    if (annotationBinding == null) {
                        if (debug.isDebugging())
                            debug.trace(String.format("Unable to resolve binding for annotation: %s", annotation)); //$NON-NLS-1$
                    } else {
                        try {
                            processComponent(type, typeBinding, annotation, annotationBinding, problems);
                        } catch (CoreException e) {
                            Activator.log(e);
                        }
                    }
                }
            }
        }

        return true;
    }

    @Override
    public boolean visit(EnumDeclaration node) {
        Annotation annotation = findComponentAnnotation(node);
        if (annotation != null)
            reportProblem(annotation, null, problems,
                    NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_enumeration,
                            node.getName().getIdentifier()),
                    node.getName().getIdentifier());

        return false;
    }

    @Override
    public boolean visit(AnnotationTypeDeclaration node) {
        Annotation annotation = findComponentAnnotation(node);
        if (annotation != null)
            reportProblem(annotation, null, problems,
                    NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_annotation,
                            node.getName().getIdentifier()),
                    node.getName().getIdentifier());

        return true;
    }

    private Annotation findComponentAnnotation(AbstractTypeDeclaration type) {
        for (Object item : type.modifiers()) {
            if (!(item instanceof Annotation))
                continue;

            Annotation annotation = (Annotation) item;
            IAnnotationBinding annotationBinding = annotation.resolveAnnotationBinding();
            if (annotationBinding == null) {
                if (debug.isDebugging())
                    debug.trace(String.format("Unable to resolve binding for annotation: %s", annotation)); //$NON-NLS-1$

                continue;
            }

            if (COMPONENT_ANNOTATION.equals(annotationBinding.getAnnotationType().getQualifiedName()))
                return annotation;
        }

        return null;
    }

    private boolean isNestedPublicStatic(AbstractTypeDeclaration type) {
        if (Modifier.isStatic(type.getModifiers())) {
            ASTNode parent = type.getParent();
            if (parent != null && (parent.getNodeType() == ASTNode.TYPE_DECLARATION
                    || parent.getNodeType() == ASTNode.ANNOTATION_TYPE_DECLARATION)) {
                AbstractTypeDeclaration parentType = (AbstractTypeDeclaration) parent;
                if (Modifier.isPublic(parentType.getModifiers()))
                    return parentType.isPackageMemberTypeDeclaration() || isNestedPublicStatic(parentType);
            }
        }

        return false;
    }

    private boolean hasDefaultConstructor(TypeDeclaration type) {
        boolean hasConstructor = false;
        for (MethodDeclaration method : type.getMethods()) {
            if (method.isConstructor()) {
                hasConstructor = true;
                if (Modifier.isPublic(method.getModifiers()) && method.parameters().isEmpty())
                    return true;
            }
        }

        return !hasConstructor;
    }

    private void processComponent(TypeDeclaration type, ITypeBinding typeBinding, Annotation annotation,
            IAnnotationBinding annotationBinding, Collection<DSAnnotationProblem> problems) throws CoreException {
        // determine component name
        HashMap<String, Object> params = new HashMap<>();
        for (IMemberValuePairBinding pair : annotationBinding.getDeclaredMemberValuePairs()) {
            params.put(pair.getName(), pair.getValue());
        }

        String implClass = typeBinding.getBinaryName();

        String name = implClass;
        Object value;
        if ((value = params.get("name")) instanceof String) { //$NON-NLS-1$
            name = (String) value;
            validateComponentName(annotation, name, problems);
        }

        // set up document to edit
        IPath path = new Path(state.getPath()).append(name).addFileExtension("xml"); //$NON-NLS-1$

        String dsKey = path.toPortableString();
        dsKeys.put(implClass, dsKey);

        IProject project = typeBinding.getJavaElement().getJavaProject().getProject();
        IFile file = PDEProject.getBundleRelativeFile(project, path);
        IPath filePath = file.getFullPath();

        processor.verifyOutputLocation(file);

        // handle file move/rename
        String oldPath = state.getModelFile(implClass);
        if (oldPath != null && !oldPath.equals(dsKey) && !file.exists()) {
            IFile oldFile = PDEProject.getBundleRelativeFile(project, Path.fromPortableString(oldPath));
            if (oldFile.exists()) {
                try {
                    oldFile.move(file.getFullPath(), true, true, null);
                } catch (CoreException e) {
                    Activator.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, String.format(
                            "Unable to move model file from '%s' to '%s'.", oldPath, file.getFullPath()), e)); //$NON-NLS-1$
                }
            }
        }

        ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager();
        bufferManager.connect(filePath, LocationKind.IFILE, null);
        ITextFileBuffer buffer = bufferManager.getTextFileBuffer(filePath, LocationKind.IFILE);
        if (buffer.isDirty())
            buffer.commit(null, true);

        IDocument document = buffer.getDocument();

        final DSModel dsModel = new DSModel(document, true);
        dsModel.setUnderlyingResource(file);
        dsModel.setCharset("UTF-8"); //$NON-NLS-1$
        dsModel.load();

        // note: we can't use XMLTextChangeListener because it generates overlapping edits!
        // thus we replace the entire content with one edit (if changed)
        final IDocument fDoc = document;
        dsModel.addModelChangedListener(new IModelTextChangeListener() {

            private final IDocument document = fDoc;

            private boolean changed;

            @Override
            public void modelChanged(IModelChangedEvent event) {
                changed = true;
            }

            @Override
            public TextEdit[] getTextOperations() {
                if (!changed)
                    return new TextEdit[0];

                String text = dsModel.getContents();
                ReplaceEdit edit = new ReplaceEdit(0, document.getLength(), text);
                return new TextEdit[] { edit };
            }

            @Override
            public String getReadableName(TextEdit edit) {
                return null;
            }
        });

        try {
            processComponent(dsModel, type, typeBinding, annotation, annotationBinding, params, name, implClass,
                    problems);

            TextEdit[] edits = dsModel.getLastTextChangeListener().getTextOperations();
            if (edits.length > 0) {
                if (debug.isDebugging())
                    debug.trace(String.format("Saving model: %s", file.getFullPath())); //$NON-NLS-1$

                final MultiTextEdit edit = new MultiTextEdit();
                edit.addChildren(edits);

                if (buffer.isSynchronizationContextRequested()) {
                    final IDocument doc = document;
                    final CoreException[] ex = new CoreException[1];
                    final CountDownLatch latch = new CountDownLatch(1);
                    bufferManager.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                performEdit(doc, edit);
                            } catch (CoreException e) {
                                ex[0] = e;
                            }

                            latch.countDown();
                        }
                    });

                    try {
                        latch.await();
                    } catch (InterruptedException e) {
                        if (debug.isDebugging())
                            debug.trace("Interrupted while waiting for edits to complete on display thread.", e); //$NON-NLS-1$
                    }

                    if (ex[0] != null)
                        throw ex[0];
                } else {
                    performEdit(document, edit);
                }

                buffer.commit(null, true);
            }
        } finally {
            dsModel.dispose();
            bufferManager.disconnect(buffer.getLocation(), LocationKind.IFILE, null);
        }
    }

    private void performEdit(IDocument document, TextEdit edit) throws CoreException {
        DocumentRewriteSession session = null;
        try {
            if (document instanceof IDocumentExtension4) {
                session = ((IDocumentExtension4) document)
                        .startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED);
            }

            LinkedModeModel.closeAllModels(document);
            edit.apply(document);
        } catch (MalformedTreeException e) {
            throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
                    "Error applying changes to component model.", e)); //$NON-NLS-1$
        } catch (BadLocationException e) {
            throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
                    "Error applying changes to component model.", e)); //$NON-NLS-1$
        } finally {
            if (session != null) {
                ((IDocumentExtension4) document).stopRewriteSession(session);
            }
        }
    }

    private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding typeBinding,
            Annotation annotation, IAnnotationBinding annotationBinding, Map<String, ?> params, String name,
            String implClass, Collection<DSAnnotationProblem> problems) {
        Object value;
        Collection<String> services;
        if ((value = params.get("service")) instanceof Object[]) { //$NON-NLS-1$
            Object[] elements = (Object[]) value;
            services = new LinkedHashSet<>(elements.length);
            Map<String, Integer> serviceDuplicates = errorLevel.isIgnore() ? null : new HashMap<>();
            for (int i = 0; i < elements.length; ++i) {
                ITypeBinding serviceType = (ITypeBinding) elements[i];
                String serviceName = serviceType.getBinaryName();
                if (!errorLevel.isIgnore()) {
                    if (serviceDuplicates.containsKey(serviceName)) {
                        reportProblem(annotation, "service", i, problems, //$NON-NLS-1$
                                Messages.AnnotationProcessor_duplicateServiceDeclaration, serviceName);
                        Integer pos = serviceDuplicates.put(serviceName, null);
                        if (pos != null)
                            reportProblem(annotation, "service", pos.intValue(), problems, //$NON-NLS-1$
                                    Messages.AnnotationProcessor_duplicateServiceDeclaration, serviceName);
                    } else {
                        serviceDuplicates.put(serviceName, i);
                    }
                }

                services.add(serviceName);
                validateComponentService(annotation, typeBinding, serviceType, i, problems);
            }
        } else {
            ITypeBinding[] serviceTypes = typeBinding.getInterfaces();
            services = new ArrayList<>(serviceTypes.length);
            for (int i = 0; i < serviceTypes.length; ++i) {
                services.add(serviceTypes[i].getBinaryName());
            }
        }

        String factory = null;
        if ((value = params.get("factory")) instanceof String) { //$NON-NLS-1$
            factory = (String) value;
            validateComponentFactory(annotation, factory, problems);
        }

        Boolean serviceFactory = null;
        if ((value = params.get("servicefactory")) instanceof Boolean) { //$NON-NLS-1$
            serviceFactory = (Boolean) value;
        }

        Boolean enabled = null;
        if ((value = params.get("enabled")) instanceof Boolean) { //$NON-NLS-1$
            enabled = (Boolean) value;
        }

        Boolean immediate = null;
        if ((value = params.get("immediate")) instanceof Boolean) { //$NON-NLS-1$
            immediate = (Boolean) value;
        }

        String[] properties;
        if ((value = params.get("property")) instanceof Object[]) { //$NON-NLS-1$
            Object[] elements = (Object[]) value;
            ArrayList<String> list = new ArrayList<>(elements.length);
            for (int i = 0; i < elements.length; ++i) {
                if (elements[i] instanceof String)
                    list.add((String) elements[i]);
            }

            properties = list.toArray(new String[list.size()]);
        } else {
            properties = new String[0];
        }

        String[] propertyFiles;
        if ((value = params.get("properties")) instanceof Object[]) { //$NON-NLS-1$
            Object[] elements = (Object[]) value;
            ArrayList<String> list = new ArrayList<>(elements.length);
            for (int i = 0; i < elements.length; ++i) {
                if (elements[i] instanceof String)
                    list.add((String) elements[i]);
            }

            propertyFiles = list.toArray(new String[list.size()]);
            validateComponentPropertyFiles(annotation,
                    ((IType) typeBinding.getJavaElement()).getJavaProject().getProject(), propertyFiles, problems);
        } else {
            propertyFiles = new String[0];
        }

        String configPolicy = null;
        if ((value = params.get("configurationPolicy")) instanceof IVariableBinding) { //$NON-NLS-1$
            IVariableBinding configPolicyBinding = (IVariableBinding) value;
            ConfigurationPolicy configPolicyLiteral = ConfigurationPolicy.valueOf(configPolicyBinding.getName());
            if (configPolicyLiteral != null)
                configPolicy = configPolicyLiteral.toString();
        }

        String configPid = null;
        if ((value = params.get("configurationPid")) instanceof String) { //$NON-NLS-1$
            configPid = (String) value;
            validateComponentConfigPID(annotation, configPid, problems);
        }

        IDSComponent component = model.getDSComponent();

        if (enabled == null) {
            removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_ENABLED, IDSConstants.VALUE_TRUE);
        } else {
            component.setEnabled(enabled.booleanValue());
        }

        if (name == null) {
            removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_NAME, null);
        } else {
            component.setAttributeName(name);
        }

        if (factory == null) {
            removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_FACTORY, null);
        } else {
            component.setFactory(factory);
        }

        if (immediate == null) {
            removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_IMMEDIATE, null);
        } else {
            component.setImmediate(immediate.booleanValue());
        }

        if (configPolicy == null) {
            removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_CONFIGURATION_POLICY,
                    IDSConstants.VALUE_CONFIGURATION_POLICY_OPTIONAL);
        } else {
            component.setConfigurationPolicy(configPolicy);
        }

        IDSDocumentFactory dsFactory = model.getFactory();

        IDSProperty[] propElements = component.getPropertyElements();
        if (properties.length == 0) {
            removeChildren(component, Arrays.asList(propElements));
        } else {
            // build up new property elements
            LinkedHashMap<String, IDSProperty> map = new LinkedHashMap<>(properties.length);
            for (int i = 0; i < properties.length; ++i) {
                String propertyStr = properties[i];
                String[] pair = propertyStr.split("=", 2); //$NON-NLS-1$
                int colon = pair[0].indexOf(':');
                String propertyName, propertyType;
                if (colon == -1) {
                    propertyName = pair[0];
                    propertyType = null;
                } else {
                    propertyName = pair[0].substring(0, colon);
                    propertyType = pair[0].substring(colon + 1);
                }

                String propertyValue = pair.length > 1 ? pair[1].trim() : null;

                IDSProperty property = map.get(propertyName);
                if (property == null) {
                    // create a new property
                    property = dsFactory.createProperty();
                    map.put(propertyName, property);
                    property.setPropertyName(propertyName);
                    if (propertyType == null)
                        removeAttribute(property, IDSConstants.ATTRIBUTE_PROPERTY_TYPE, null); // just remove the attribute completely so we can detect changes when reconciling
                    else
                        property.setPropertyType(propertyType);

                    property.setPropertyValue(propertyValue);
                    validateComponentProperty(annotation, propertyName, propertyType, propertyValue, i, problems);
                } else {
                    // property is multi-valued
                    String content = property.getPropertyElemBody();
                    if (content == null) {
                        content = property.getPropertyValue();
                        property.setPropertyElemBody(content);
                        property.setPropertyValue(null);
                    }

                    if (!errorLevel.isIgnore()) {
                        String expected = property.getPropertyType() == null
                                || property.getPropertyType().length() == 0
                                || IDSConstants.VALUE_PROPERTY_TYPE_STRING.equals(property.getPropertyType())
                                        ? Messages.AnnotationProcessor_stringOrEmpty
                                        : property.getPropertyType();
                        String actual = propertyType == null
                                || IDSConstants.VALUE_PROPERTY_TYPE_STRING.equals(propertyType)
                                        ? Messages.AnnotationProcessor_stringOrEmpty
                                        : propertyType;
                        if (!actual.equals(expected))
                            reportProblem(annotation, "property", i, problems, //$NON-NLS-1$
                                    NLS.bind(Messages.AnnotationProcessor_inconsistentComponentPropertyType, actual,
                                            expected),
                                    actual);
                        else
                            validateComponentProperty(annotation, propertyName, propertyType, propertyValue, i,
                                    problems);
                    }

                    if (propertyValue != null)
                        property.setPropertyElemBody(content + "\n" + pair[1]); //$NON-NLS-1$
                }
            }

            // reconcile against existing property elements
            HashMap<String, IDSProperty> propMap = new HashMap<>(propElements.length);
            for (IDSProperty propElement : propElements) {
                propMap.put(propElement.getPropertyName(), propElement);
            }

            ArrayList<IDSProperty> propList = new ArrayList<>(map.values());
            for (ListIterator<IDSProperty> i = propList.listIterator(); i.hasNext();) {
                IDSProperty newProperty = i.next();
                IDSProperty property = propMap.remove(newProperty.getPropertyName());
                if (property == null)
                    continue;

                i.set(property);

                String newPropertyType = newProperty.getPropertyType();
                if (newPropertyType != null
                        || !IDSConstants.VALUE_PROPERTY_TYPE_STRING.equals(property.getPropertyType()))
                    property.setPropertyType(newPropertyType);

                String newContent = newProperty.getPropertyElemBody();
                if (newContent == null) {
                    property.setPropertyValue(newProperty.getPropertyValue());
                    IDocumentTextNode textNode = property.getTextNode();
                    if (textNode != null) {
                        property.removeTextNode();
                        if (property.isInTheModel() && property.isEditable()) {
                            model.fireModelChanged(new ModelChangedEvent(model, IModelChangedEvent.REMOVE,
                                    new Object[] { textNode }, null));
                        }
                    }
                } else {
                    removeAttribute(property, IDSConstants.ATTRIBUTE_PROPERTY_VALUE, null);
                    String content = property.getPropertyElemBody();
                    if (content == null || !newContent.equals(normalizePropertyElemBody(content))) {
                        property.setPropertyElemBody(newContent);
                    }
                }
            }

            int firstPos = propElements.length == 0 ? 0 // insert first property element as first child of component
                    : component.indexOf(propElements[0]);
            removeChildren(component, propMap.values());

            addOrMoveChildren(component, propList, firstPos);
        }

        IDSProperties[] propFileElements = component.getPropertiesElements();
        if (propertyFiles.length == 0) {
            removeChildren(component, Arrays.asList(propFileElements));
        } else {
            HashMap<String, IDSProperties> propFileMap = new HashMap<>(propFileElements.length);
            for (IDSProperties propFileElement : propFileElements) {
                propFileMap.put(propFileElement.getEntry(), propFileElement);
            }

            ArrayList<IDSProperties> propFileList = new ArrayList<>(propertyFiles.length);
            for (String propertyFile : propertyFiles) {
                IDSProperties propertiesElement = propFileMap.remove(propertyFile);
                if (propertiesElement == null) {
                    propertiesElement = dsFactory.createProperties();
                    propertiesElement.setInTheModel(false); // note: workaround for PDE bug
                    propertiesElement.setEntry(propertyFile);
                }

                propFileList.add(propertiesElement);
            }

            int firstPos;
            if (propFileElements.length == 0) {
                // insert first properties element after last property or (if none) first child of component
                propElements = component.getPropertyElements();
                firstPos = propElements.length == 0 ? 0
                        : component.indexOf(propElements[propElements.length - 1]) + 1;
            } else {
                firstPos = component.indexOf(propFileElements[0]);
            }

            removeChildren(component, propFileMap.values());

            addOrMoveChildren(component, propFileList, firstPos);
        }

        IDSService service = component.getService();
        if (services.isEmpty()) {
            if (service != null)
                component.removeService(service);
        } else {
            if (service == null) {
                service = dsFactory.createService();

                // insert service element after last property or properties element
                int firstPos = Math.max(0, indexOfLastPropertyOrProperties(component));
                component.addChildNode(service, firstPos, true);
            }

            IDSProvide[] provides = service.getProvidedServices();
            HashMap<String, IDSProvide> provideMap = new HashMap<>(provides.length);
            for (IDSProvide provide : provides) {
                provideMap.put(provide.getInterface(), provide);
            }

            ArrayList<IDSProvide> provideList = new ArrayList<>(services.size());
            for (String serviceName : services) {
                IDSProvide provide = provideMap.remove(serviceName);
                if (provide == null) {
                    provide = dsFactory.createProvide();
                    provide.setInterface(serviceName);
                }

                provideList.add(provide);
            }

            int firstPos = provides.length == 0 ? -1 : service.indexOf(provides[0]);
            removeChildren(service, (provideMap.values()));

            addOrMoveChildren(service, provideList, firstPos);

            if (serviceFactory == null) {
                removeAttribute(service, IDSConstants.ATTRIBUTE_SERVICE_FACTORY, IDSConstants.VALUE_FALSE);
            } else {
                service.setServiceFactory(serviceFactory.booleanValue());
            }
        }

        boolean requiresV12 = false;
        String activate = null;
        Annotation activateAnnotation = null;
        String deactivate = null;
        Annotation deactivateAnnotation = null;
        String modified = null;
        Annotation modifiedAnnotation = null;

        ArrayList<IDSReference> references = new ArrayList<>();
        HashMap<String, Annotation> referenceNames = new HashMap<>();
        IDSReference[] refElements = component.getReferences();

        HashMap<String, IDSReference> refMap = new HashMap<>(refElements.length);
        for (IDSReference refElement : refElements) {
            refMap.put(refElement.getReferenceBind(), refElement);
        }

        for (MethodDeclaration method : type.getMethods()) {
            for (Object modifier : method.modifiers()) {
                if (!(modifier instanceof Annotation))
                    continue;

                Annotation methodAnnotation = (Annotation) modifier;
                IAnnotationBinding methodAnnotationBinding = methodAnnotation.resolveAnnotationBinding();
                if (methodAnnotationBinding == null) {
                    if (debug.isDebugging())
                        debug.trace(
                                String.format("Unable to resolve binding for annotation: %s", methodAnnotation)); //$NON-NLS-1$

                    continue;
                }

                String annotationName = methodAnnotationBinding.getAnnotationType().getQualifiedName();

                if (ACTIVATE_ANNOTATION.equals(annotationName)) {
                    if (activate == null) {
                        activate = method.getName().getIdentifier();
                        activateAnnotation = methodAnnotation;
                        validateLifeCycleMethod(methodAnnotation, "activate", method, problems); //$NON-NLS-1$
                    } else if (!errorLevel.isIgnore()) {
                        reportProblem(methodAnnotation, null, problems,
                                Messages.AnnotationProcessor_duplicateActivateMethod,
                                method.getName().getIdentifier());
                        if (activateAnnotation != null) {
                            reportProblem(activateAnnotation, null, problems,
                                    Messages.AnnotationProcessor_duplicateActivateMethod, activate);
                            activateAnnotation = null;
                        }
                    }

                    continue;
                }

                if (DEACTIVATE_ANNOTATION.equals(annotationName)) {
                    if (deactivate == null) {
                        deactivate = method.getName().getIdentifier();
                        deactivateAnnotation = methodAnnotation;
                        validateLifeCycleMethod(methodAnnotation, "deactivate", method, problems); //$NON-NLS-1$
                    } else if (!errorLevel.isIgnore()) {
                        reportProblem(methodAnnotation, null, problems,
                                Messages.AnnotationProcessor_duplicateDeactivateMethod,
                                method.getName().getIdentifier());
                        if (deactivateAnnotation != null) {
                            reportProblem(deactivateAnnotation, null, problems,
                                    Messages.AnnotationProcessor_duplicateDeactivateMethod, deactivate);
                            deactivateAnnotation = null;
                        }
                    }

                    continue;
                }

                if (MODIFIED_ANNOTATION.equals(annotationName)) {
                    if (modified == null) {
                        modified = method.getName().getIdentifier();
                        modifiedAnnotation = methodAnnotation;
                        validateLifeCycleMethod(methodAnnotation, "modified", method, problems); //$NON-NLS-1$
                    } else if (!errorLevel.isIgnore()) {
                        reportProblem(methodAnnotation, null, problems,
                                Messages.AnnotationProcessor_duplicateModifiedMethod,
                                method.getName().getIdentifier());
                        if (modifiedAnnotation != null) {
                            reportProblem(modifiedAnnotation, null, problems,
                                    Messages.AnnotationProcessor_duplicateModifiedMethod, modified);
                            modifiedAnnotation = null;
                        }
                    }

                    continue;
                }

                if (REFERENCE_ANNOTATION.equals(annotationName)) {
                    IMethodBinding methodBinding = method.resolveBinding();
                    if (methodBinding == null) {
                        if (debug.isDebugging())
                            debug.trace(String.format("Unable to resolve binding for method: %s", method)); //$NON-NLS-1$
                    } else {
                        requiresV12 |= processReference(method, methodBinding, methodAnnotation,
                                methodAnnotationBinding, refMap, dsFactory, references, referenceNames, problems);
                    }

                    continue;
                }
            }
        }

        if (activate == null) {
            // only remove activate="activate" if method not found
            if (!"activate".equals(component.getActivateMethod()) //$NON-NLS-1$
                    || !hasLifeCycleMethod(typeBinding, "activate")) //$NON-NLS-1$
                removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_ACTIVATE, null); //$NON-NLS-1$
        } else {
            component.setActivateMethod(activate);
        }

        if (deactivate == null) {
            // only remove deactivate="deactivate" if method not found
            if (!"deactivate".equals(component.getDeactivateMethod()) //$NON-NLS-1$
                    || !hasLifeCycleMethod(typeBinding, "deactivate")) //$NON-NLS-1$
                removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_DEACTIVATE, null); //$NON-NLS-1$
        } else {
            component.setDeactivateMethod(deactivate);
        }

        if (modified == null) {
            removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_MODIFIED, null);
        } else {
            component.setModifiedeMethod(modified);
        }

        if (configPid == null) {
            removeAttribute(component, ATTRIBUTE_COMPONENT_CONFIGURATION_PID, null);
        } else {
            component.setXMLAttribute(ATTRIBUTE_COMPONENT_CONFIGURATION_PID, configPid);
            requiresV12 = true;
        }

        if (references.isEmpty()) {
            removeChildren(component, Arrays.asList(refElements));
        } else {
            // references must be declared in ascending lexicographical order of their names
            Collections.sort(references, REF_NAME_COMPARATOR);

            int firstPos;
            if (refElements.length == 0) {
                // insert first reference element after service element, or (if not present) last property or properties
                service = component.getService();
                if (service == null) {
                    firstPos = Math.max(0, indexOfLastPropertyOrProperties(component));
                } else {
                    firstPos = component.indexOf(service) + 1;
                }
            } else {
                firstPos = component.indexOf(refElements[0]);
            }

            removeChildren(component, refMap.values());

            addOrMoveChildren(component, references, firstPos);
        }

        IDSImplementation impl = component.getImplementation();
        if (impl == null) {
            impl = dsFactory.createImplementation();
            component.setImplementation(impl);
        }

        impl.setClassName(implClass);

        String xmlns = NAMESPACE_1_1;
        if ((value = params.get("xmlns")) instanceof String) { //$NON-NLS-1$
            xmlns = (String) value;
            validateComponentXMLNS(annotation, xmlns, requiresV12, problems);
        } else if (requiresV12) {
            xmlns = NAMESPACE_1_2;
        }

        component.setNamespace(xmlns);
    }

    private void removeChildren(IDSObject parent, Collection<? extends IDocumentElementNode> children) {
        for (IDocumentElementNode child : children) {
            parent.removeChildNode(child, true);
        }
    }

    private void removeAttribute(IDSObject obj, String name, String defaultValue) {
        IDocumentAttributeNode attrNode = obj.getDocumentAttribute(name);
        if (attrNode != null) {
            // only remove if value is not default
            String value = attrNode.getAttributeValue();
            if (value != null && value.equals(defaultValue))
                return;

            obj.removeDocumentAttribute(attrNode);
            if (obj.isInTheModel() && obj.isEditable()) {
                obj.getModel().fireModelChanged(new ModelChangedEvent(obj.getModel(), ModelChangedEvent.REMOVE,
                        new Object[] { attrNode }, null));
            }
        }
    }

    private void addOrMoveChildren(IDSObject parent, List<? extends IDSObject> children, int firstPos) {
        for (int i = 0, n = children.size(); i < n; ++i) {
            IDSObject child = children.get(i);
            if (child.isInTheModel()) {
                int pos = parent.indexOf(child);
                if (i == 0) {
                    if (firstPos < pos) {
                        // move to first place
                        moveChildNode(parent, child, firstPos - pos, true);
                    }
                } else {
                    int prevPos = parent.indexOf(children.get(i - 1));
                    if (prevPos > pos) {
                        // move to previous sibling's position
                        moveChildNode(parent, child, prevPos - pos, true);
                    }
                }
            } else {
                if (i == 0) {
                    if (firstPos == -1) {
                        parent.addChildNode(child, true);
                    } else {
                        // insert into first place
                        parent.addChildNode(child, firstPos, true);
                    }
                } else {
                    // insert after preceding sibling
                    parent.addChildNode(child, parent.indexOf(children.get(i - 1)) + 1, true);
                }
            }
        }
    }

    private void moveChildNode(IDocumentObject obj, IDocumentElementNode node, int newRelativeIndex,
            boolean fireEvent) {
        if (newRelativeIndex == 1 || newRelativeIndex == -1) {
            obj.moveChildNode(node, newRelativeIndex, fireEvent);
            return;
        }

        // workaround for PDE's busted DocumentObject.clone() method
        int currentIndex = obj.indexOf(node);
        if (currentIndex == -1)
            return;

        int newIndex = newRelativeIndex + currentIndex;
        if (newIndex < 0 || newIndex >= obj.getChildCount())
            return;

        obj.removeChildNode(node, fireEvent);
        IDocumentElementNode clone = clone(obj, node);
        obj.addChildNode(clone, newIndex, fireEvent);
    }

    private IDocumentElementNode clone(IDocumentObject obj, IDocumentElementNode node) {
        // note: same exact impl as DocumentObject.clone()
        // but here the deserialized object will actually resolve successfully
        // because our classloader (with DSPropery visible) will be on top of the stack
        // yay for Java serialization, *sigh*
        IDocumentElementNode clone = null;
        try {
            // Serialize
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bout);
            out.writeObject(node);
            out.flush();
            out.close();
            byte[] bytes = bout.toByteArray();
            // Deserialize
            ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
            ObjectInputStream in = new ObjectInputStream(bin);
            clone = (IDocumentElementNode) in.readObject();
            in.close();
            // Reconnect
            clone.reconnect(obj, obj.getSharedModel());
        } catch (IOException e) {
            if (debug.isDebugging())
                debug.trace("Error cloning element.", e); //$NON-NLS-1$
        } catch (ClassNotFoundException e) {
            if (debug.isDebugging())
                debug.trace("Error cloning element.", e); //$NON-NLS-1$
        }

        return clone;
    }

    private int indexOfLastPropertyOrProperties(IDSComponent component) {
        int pos = -1;
        IDSProperty[] propElements = component.getPropertyElements();
        IDSProperties[] propFileElements = component.getPropertiesElements();
        if (propElements.length > 0)
            pos = component.indexOf(propElements[propElements.length - 1]) + 1;

        if (propFileElements.length > 0) {
            int lastPos = component.indexOf(propFileElements[propFileElements.length - 1]) + 1;
            if (lastPos > pos)
                pos = lastPos;
        }

        return pos;
    }

    private String normalizePropertyElemBody(String content) {
        StringBuilder buf = new StringBuilder(content.length());
        BufferedReader reader = new BufferedReader(new StringReader(content));
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                String trimmed = line.trim();
                if (trimmed.length() == 0)
                    continue;

                if (buf.length() > 0)
                    buf.append('\n');

                buf.append(trimmed);
            }
        } catch (IOException e) {
            if (debug.isDebugging())
                debug.trace("Error reading property element body.", e); //$NON-NLS-1$
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                // ignore
            }
        }

        return buf.toString();
    }

    private void validateComponentName(Annotation annotation, String name,
            Collection<DSAnnotationProblem> problems) {
        if (!errorLevel.isIgnore() && !PID_PATTERN.matcher(name).matches())
            reportProblem(annotation, "name", problems, //$NON-NLS-1$
                    NLS.bind(Messages.AnnotationProcessor_invalidComponentName, name), name);
    }

    private void validateComponentService(Annotation annotation, ITypeBinding componentType,
            ITypeBinding serviceType, int index, Collection<DSAnnotationProblem> problems) {
        if (!errorLevel.isIgnore() && !componentType.isAssignmentCompatible(serviceType))
            reportProblem(annotation, "service", problems, //$NON-NLS-1$
                    NLS.bind(Messages.AnnotationProcessor_invalidComponentService, serviceType.getName()),
                    serviceType.getName());
    }

    private void validateComponentFactory(Annotation annotation, String factory,
            Collection<DSAnnotationProblem> problems) {
        if (!errorLevel.isIgnore() && !PID_PATTERN.matcher(factory).matches())
            reportProblem(annotation, "factory", problems, //$NON-NLS-1$
                    NLS.bind(Messages.AnnotationProcessor_invalidComponentFactoryName, factory), factory);
    }

    private void validateComponentProperty(Annotation annotation, String name, String type, String value, int index,
            Collection<DSAnnotationProblem> problems) {
        if (errorLevel.isIgnore())
            return;

        if (PROPERTY_TYPES.contains(type)) {
            if (name == null || name.trim().length() == 0)
                reportProblem(annotation, "property", index, problems, //$NON-NLS-1$
                        Messages.AnnotationProcessor_invalidComponentProperty_nameRequired, name);

            if (value == null) {
                reportProblem(annotation, "property", index, problems, //$NON-NLS-1$
                        Messages.AnnotationProcessor_invalidComponentProperty_valueRequired, name);
            } else {
                try {
                    if (IDSConstants.VALUE_PROPERTY_TYPE_LONG.equals(type))
                        Long.valueOf(value);
                    else if (IDSConstants.VALUE_PROPERTY_TYPE_DOUBLE.equals(type))
                        Double.valueOf(value);
                    else if (IDSConstants.VALUE_PROPERTY_TYPE_FLOAT.equals(type))
                        Float.valueOf(value);
                    else if (IDSConstants.VALUE_PROPERTY_TYPE_INTEGER.equals(type)
                            || IDSConstants.VALUE_PROPERTY_TYPE_CHAR.equals(type))
                        Integer.valueOf(value);
                    else if (IDSConstants.VALUE_PROPERTY_TYPE_BYTE.equals(type))
                        Byte.valueOf(value);
                    else if (IDSConstants.VALUE_PROPERTY_TYPE_SHORT.equals(type))
                        Short.valueOf(value);
                } catch (NumberFormatException e) {
                    reportProblem(annotation, "property", index, problems, //$NON-NLS-1$
                            NLS.bind(Messages.AnnotationProcessor_invalidComponentPropertyValue, type, value),
                            String.valueOf(value));
                }
            }
        } else {
            reportProblem(annotation, "property", index, problems, //$NON-NLS-1$
                    NLS.bind(Messages.AnnotationProcessor_invalidComponentPropertyType, type),
                    String.valueOf(type));
        }
    }

    private void validateComponentPropertyFiles(Annotation annotation, IProject project, String[] files,
            Collection<DSAnnotationProblem> problems) {
        if (errorLevel.isIgnore())
            return;

        for (int i = 0; i < files.length; ++i) {
            String file = files[i];
            IFile wsFile = PDEProject.getBundleRelativeFile(project, new Path(file));
            if (!wsFile.exists())
                reportProblem(annotation, "properties", i, problems, //$NON-NLS-1$
                        NLS.bind(Messages.AnnotationProcessor_invalidComponentPropertyFile, file), file);
        }
    }

    private void validateComponentXMLNS(Annotation annotation, String xmlns, boolean requiresV12,
            Collection<DSAnnotationProblem> problems) {
        if (!errorLevel.isIgnore() && (requiresV12 || !NAMESPACE_1_1.equals(xmlns)) && !NAMESPACE_1_2.equals(xmlns))
            reportProblem(annotation, "xmlns", problems, //$NON-NLS-1$
                    NLS.bind(Messages.AnnotationProcessor_invalidComponentDescriptorNamespace, xmlns), xmlns);
    }

    private void validateComponentConfigPID(Annotation annotation, String configPid,
            Collection<DSAnnotationProblem> problems) {
        if (!errorLevel.isIgnore() && !PID_PATTERN.matcher(configPid).matches())
            reportProblem(annotation, "configurationPid", problems, //$NON-NLS-1$
                    NLS.bind(Messages.AnnotationProcessor_invalidComponentConfigurationPid, configPid), configPid);
    }

    private void validateLifeCycleMethod(Annotation annotation, String methodName, MethodDeclaration method,
            Collection<DSAnnotationProblem> problems) {
        if (errorLevel.isIgnore())
            return;

        IMethodBinding methodBinding = method.resolveBinding();
        if (methodBinding == null) {
            if (debug.isDebugging())
                debug.trace(String.format("Unable to resolve binding for method: %s", method)); //$NON-NLS-1$

            return;
        }

        String returnTypeName = methodBinding.getReturnType().getName();
        if (!Void.TYPE.getName().equals(returnTypeName))
            reportProblem(annotation, methodName, problems,
                    NLS.bind(Messages.AnnotationProcessor_invalidLifeCycleMethodReturnType, methodName,
                            returnTypeName),
                    returnTypeName);

        ITypeBinding[] paramTypeBindings = methodBinding.getParameterTypes();

        if (paramTypeBindings.length == 0)
            // no-arg method
            return;

        // every argument must be either Map, ComponentContext, or BundleContext
        boolean hasMap = false;
        boolean hasCompCtx = false;
        boolean hasBundleCtx = false;
        boolean hasInt = false;
        for (ITypeBinding paramTypeBinding : paramTypeBindings) {
            String paramTypeName = paramTypeBinding.getErasure().getQualifiedName();
            boolean isDuplicate = false;

            if (Map.class.getName().equals(paramTypeName)) {
                if (hasMap)
                    isDuplicate = true;
                else
                    hasMap = true;
            } else if (ComponentContext.class.getName().equals(paramTypeName)) {
                if (hasCompCtx)
                    isDuplicate = true;
                else
                    hasCompCtx = true;
            } else if (BundleContext.class.getName().equals(paramTypeName)) {
                if (hasBundleCtx)
                    isDuplicate = true;
                else
                    hasBundleCtx = true;
            } else if ("deactivate".equals(methodName) //$NON-NLS-1$
                    && (Integer.class.getName().equals(paramTypeName)
                            || Integer.TYPE.getName().equals(paramTypeName))) {
                if (hasInt)
                    isDuplicate = true;
                else
                    hasInt = true;
            } else {
                reportProblem(annotation, methodName, problems,
                        NLS.bind(Messages.AnnotationProcessor_invalidLifeCycleMethodParameterType, methodName,
                                paramTypeName),
                        paramTypeName);
            }

            if (isDuplicate)
                reportProblem(annotation, methodName, problems,
                        NLS.bind(Messages.AnnotationProcessor_duplicateLifeCycleMethodParameterType, methodName,
                                paramTypeName),
                        paramTypeName);
        }
    }

    private boolean hasLifeCycleMethod(ITypeBinding componentClass, String methodName) {
        for (IMethodBinding methodBinding : componentClass.getDeclaredMethods()) {
            if (methodName.equals(methodBinding.getName())
                    && Void.TYPE.getName().equals(methodBinding.getReturnType().getName())) {
                ITypeBinding[] paramTypeBindings = methodBinding.getParameterTypes();

                // every argument must be either Map, ComponentContext, or BundleContext
                boolean hasMap = false;
                boolean hasCompCtx = false;
                boolean hasBundleCtx = false;
                boolean hasInt = false;
                boolean isInvalid = false;
                for (ITypeBinding paramTypeBinding : paramTypeBindings) {
                    String paramTypeName = paramTypeBinding.getErasure().getQualifiedName();

                    if (Map.class.getName().equals(paramTypeName)) {
                        if (hasMap)
                            isInvalid = true;
                        else
                            hasMap = true;
                    } else if (ComponentContext.class.getName().equals(paramTypeName)) {
                        if (hasCompCtx)
                            isInvalid = true;
                        else
                            hasCompCtx = true;
                    } else if (BundleContext.class.getName().equals(paramTypeName)) {
                        if (hasBundleCtx)
                            isInvalid = true;
                        else
                            hasBundleCtx = true;
                    } else if ("deactivate".equals(methodName) //$NON-NLS-1$
                            && (Integer.class.getName().equals(paramTypeName)
                                    || Integer.TYPE.getName().equals(paramTypeName))) {
                        if (hasInt)
                            isInvalid = true;
                        else
                            hasInt = true;
                    } else {
                        isInvalid = true;
                    }

                    if (isInvalid)
                        break;
                }

                if (!isInvalid)
                    return true;
            }
        }

        return false;
    }

    private boolean processReference(MethodDeclaration method, IMethodBinding methodBinding, Annotation annotation,
            IAnnotationBinding annotationBinding, Map<String, IDSReference> refMap, IDSDocumentFactory factory,
            Collection<IDSReference> collector, Map<String, Annotation> names,
            Collection<DSAnnotationProblem> problems) {
        HashMap<String, Object> params = new HashMap<>();
        for (IMemberValuePairBinding pair : annotationBinding.getDeclaredMemberValuePairs()) {
            params.put(pair.getName(), pair.getValue());
        }

        ITypeBinding[] argTypes = methodBinding.getParameterTypes();

        ITypeBinding serviceType;
        Object value;
        if ((value = params.get("service")) instanceof ITypeBinding) { //$NON-NLS-1$
            serviceType = (ITypeBinding) value;
            if (!errorLevel.isIgnore() && argTypes.length > 0) {
                ITypeBinding[] typeArgs;
                if (!(ServiceReference.class.getName().equals(argTypes[0].getErasure().getQualifiedName())
                        && ((typeArgs = argTypes[0].getTypeArguments()).length == 0
                                || serviceType.isAssignmentCompatible(typeArgs[0])))
                        && !serviceType.isAssignmentCompatible(argTypes[0]))
                    reportProblem(annotation, "service", problems, //$NON-NLS-1$
                            NLS.bind(Messages.AnnotationProcessor_invalidReferenceService, argTypes[0].getName(),
                                    serviceType.getName()),
                            serviceType.getName());
            }
        } else if (argTypes.length > 0) {
            if (ServiceReference.class.getName().equals(argTypes[0].getErasure().getQualifiedName())) {
                ITypeBinding[] typeArgs = argTypes[0].getTypeArguments();
                if (typeArgs.length > 0)
                    serviceType = typeArgs[0];
                else
                    serviceType = null;
            } else {
                serviceType = argTypes[0].isPrimitive() ? getObjectType(method.getAST(), argTypes[0]) : argTypes[0];
            }
        } else {
            serviceType = null;
        }

        if (serviceType == null) {
            reportProblem(annotation, null, problems, Messages.AnnotationProcessor_invalidReferenceServiceUnknown);

            serviceType = method.getAST().resolveWellKnownType(Object.class.getName());
        }

        validateReferenceBindMethod(annotation, serviceType, methodBinding, problems);

        String service = serviceType == null ? null : serviceType.getBinaryName();

        String methodName = methodBinding.getName();
        String name;
        if ((value = params.get("name")) instanceof String) { //$NON-NLS-1$
            name = (String) value;
        } else if (methodName.startsWith("bind")) { //$NON-NLS-1$
            name = methodName.substring("bind".length()); //$NON-NLS-1$
        } else if (methodName.startsWith("set")) { //$NON-NLS-1$
            name = methodName.substring("set".length()); //$NON-NLS-1$
        } else if (methodName.startsWith("add")) { //$NON-NLS-1$
            name = methodName.substring("add".length()); //$NON-NLS-1$
        } else {
            name = methodName;
        }

        if (!errorLevel.isIgnore()) {
            if (names.containsKey(name)) {
                reportProblem(annotation, "name", problems, //$NON-NLS-1$
                        NLS.bind(Messages.AnnotationProcessor_duplicateReferenceName, name), name);
                Annotation duplicate = names.put(name, null);
                if (duplicate != null)
                    reportProblem(duplicate, "name", problems, //$NON-NLS-1$
                            NLS.bind(Messages.AnnotationProcessor_duplicateReferenceName, name), name);
            } else {
                names.put(name, annotation);
            }
        }

        String cardinality = null;
        if ((value = params.get("cardinality")) instanceof IVariableBinding) { //$NON-NLS-1$
            IVariableBinding cardinalityBinding = (IVariableBinding) value;
            ReferenceCardinality cardinalityLiteral = ReferenceCardinality.valueOf(cardinalityBinding.getName());
            if (cardinalityLiteral != null)
                cardinality = cardinalityLiteral.toString();
        }

        String policy = null;
        if ((value = params.get("policy")) instanceof IVariableBinding) { //$NON-NLS-1$
            IVariableBinding policyBinding = (IVariableBinding) value;
            ReferencePolicy policyLiteral = ReferencePolicy.valueOf(policyBinding.getName());
            if (policyLiteral != null)
                policy = policyLiteral.toString();
        }

        String target = null;
        if ((value = params.get("target")) instanceof String) { //$NON-NLS-1$
            target = (String) value;
            validateReferenceTarget(annotation, target, problems);
        }

        String unbind;
        if ((value = params.get("unbind")) instanceof String) { //$NON-NLS-1$
            String unbindValue = (String) value;
            if ("-".equals(unbindValue)) { //$NON-NLS-1$
                unbind = null;
            } else {
                unbind = unbindValue;
                if (!errorLevel.isIgnore()) {
                    IMethodBinding unbindMethod = findUnbindMethod(methodBinding.getDeclaringClass(), serviceType,
                            unbind, true);
                    if (unbindMethod == null)
                        reportProblem(annotation, "unbind", problems, //$NON-NLS-1$
                                NLS.bind(Messages.AnnotationProcessor_invalidReferenceUnbind, unbind), unbind);
                }
            }
        } else {
            String unbindCandidate;
            if (methodName.startsWith("add")) { //$NON-NLS-1$
                unbindCandidate = "remove" + methodName.substring("add".length()); //$NON-NLS-1$ //$NON-NLS-2$
            } else {
                unbindCandidate = "un" + methodName; //$NON-NLS-1$
            }

            IMethodBinding unbindMethod = findUnbindMethod(methodBinding.getDeclaringClass(), serviceType,
                    unbindCandidate, false);
            if (unbindMethod == null) {
                unbind = null;
                reportProblem(annotation, null, missingUnbindMethodLevel, problems,
                        NLS.bind(Messages.AnnotationProcessor_noImplicitReferenceUnbind, unbindCandidate),
                        unbindCandidate);
            } else {
                unbind = unbindMethod.getName();
            }
        }

        String policyOption = null;
        if ((value = params.get("policyOption")) instanceof IVariableBinding) { //$NON-NLS-1$
            IVariableBinding policyOptionBinding = (IVariableBinding) value;
            ReferencePolicyOption policyOptionLiteral = ReferencePolicyOption
                    .valueOf(policyOptionBinding.getName());
            if (policyOptionLiteral != null) {
                policyOption = policyOptionLiteral.toString();
            }
        }

        String updated;
        if ((value = params.get(ATTRIBUTE_REFERENCE_UPDATED)) instanceof String) {
            String updatedValue = (String) value;
            if ("-".equals(updatedValue)) { //$NON-NLS-1$
                updated = null;
            } else {
                updated = updatedValue;
                if (!errorLevel.isIgnore()) {
                    IMethodBinding updatedMethod = findUpdatedMethod(methodBinding.getDeclaringClass(), updated,
                            true);
                    if (updatedMethod == null)
                        reportProblem(annotation, ATTRIBUTE_REFERENCE_UPDATED, problems,
                                NLS.bind(Messages.AnnotationProcessor_invalidReferenceUpdated, updated), updated);
                }
            }
        } else {
            String updatedCandidate;
            if (methodName.startsWith("bind")) { //$NON-NLS-1$
                updatedCandidate = ATTRIBUTE_REFERENCE_UPDATED + methodName.substring("bind".length()); //$NON-NLS-1$
            } else if (methodName.startsWith("set")) { //$NON-NLS-1$
                updatedCandidate = ATTRIBUTE_REFERENCE_UPDATED + methodName.substring("set".length()); //$NON-NLS-1$
            } else if (methodName.startsWith("add")) { //$NON-NLS-1$
                updatedCandidate = ATTRIBUTE_REFERENCE_UPDATED + methodName.substring("add".length()); //$NON-NLS-1$
            } else {
                updatedCandidate = ATTRIBUTE_REFERENCE_UPDATED + methodName;
            }

            IMethodBinding updatedMethod = findUpdatedMethod(methodBinding.getDeclaringClass(), updatedCandidate,
                    false);
            if (updatedMethod == null)
                updated = null;
            else
                updated = updatedMethod.getName();
        }

        IDSReference reference = refMap.remove(methodName);
        if (reference == null) {
            reference = factory.createReference();
        }

        collector.add(reference);

        reference.setReferenceBind(methodName);

        if (name == null) {
            removeAttribute(reference, IDSConstants.ATTRIBUTE_REFERENCE_NAME, null);
        } else {
            reference.setReferenceName(name);
        }

        if (service == null) {
            removeAttribute(reference, IDSConstants.ATTRIBUTE_REFERENCE_INTERFACE, null);
        } else {
            reference.setReferenceInterface(service);
        }

        if (cardinality == null) {
            removeAttribute(reference, IDSConstants.ATTRIBUTE_REFERENCE_CARDINALITY,
                    IDSConstants.VALUE_REFERENCE_CARDINALITY_ONE_ONE);
        } else {
            reference.setReferenceCardinality(cardinality);
        }

        if (policy == null) {
            removeAttribute(reference, IDSConstants.ATTRIBUTE_REFERENCE_POLICY,
                    IDSConstants.VALUE_REFERENCE_POLICY_STATIC);
        } else {
            reference.setReferencePolicy(policy);
        }

        if (target == null) {
            removeAttribute(reference, IDSConstants.ATTRIBUTE_REFERENCE_TARGET, null);
        } else {
            reference.setReferenceTarget(target);
        }

        if (unbind == null) {
            removeAttribute(reference, IDSConstants.ATTRIBUTE_REFERENCE_UNBIND, null);
        } else {
            reference.setReferenceUnbind(unbind);
        }

        if (policyOption == null) {
            removeAttribute(reference, ATTRIBUTE_REFERENCE_POLICY_OPTION, VALUE_REFERENCE_POLICY_OPTION_RELUCTANT);
        } else {
            reference.setXMLAttribute(ATTRIBUTE_REFERENCE_POLICY_OPTION, policyOption);
        }

        if (updated == null) {
            removeAttribute(reference, ATTRIBUTE_REFERENCE_UPDATED, null);
        } else {
            reference.setXMLAttribute(ATTRIBUTE_REFERENCE_UPDATED, updated);
        }

        return reference.getDocumentAttribute(ATTRIBUTE_REFERENCE_POLICY_OPTION) != null
                || reference.getDocumentAttribute(ATTRIBUTE_REFERENCE_UPDATED) != null;
    }

    private ITypeBinding getObjectType(AST ast, ITypeBinding primitive) {
        if (Boolean.TYPE.getName().equals(primitive.getName()))
            return ast.resolveWellKnownType(Boolean.class.getName());

        if (Byte.TYPE.getName().equals(primitive.getName()))
            return ast.resolveWellKnownType(Byte.class.getName());

        if (Character.TYPE.getName().equals(primitive.getName()))
            return ast.resolveWellKnownType(Character.class.getName());

        if (Double.TYPE.getName().equals(primitive.getName()))
            return ast.resolveWellKnownType(Double.class.getName());

        if (Float.TYPE.getName().equals(primitive.getName()))
            return ast.resolveWellKnownType(Float.class.getName());

        if (Integer.TYPE.getName().equals(primitive.getName()))
            return ast.resolveWellKnownType(Integer.class.getName());

        if (Long.TYPE.getName().equals(primitive.getName()))
            return ast.resolveWellKnownType(Long.class.getName());

        if (Short.TYPE.getName().equals(primitive.getName()))
            return ast.resolveWellKnownType(Short.class.getName());

        return null;
    }

    private void validateReferenceBindMethod(Annotation annotation, ITypeBinding serviceType,
            IMethodBinding methodBinding, Collection<DSAnnotationProblem> problems) {
        if (errorLevel.isIgnore())
            return;

        String returnTypeName = methodBinding.getReturnType().getName();
        if (!Void.TYPE.getName().equals(returnTypeName))
            reportProblem(annotation, null, problems,
                    NLS.bind(Messages.AnnotationProcessor_invalidBindMethodReturnType, returnTypeName),
                    returnTypeName);

        ITypeBinding[] paramTypeBindings = methodBinding.getParameterTypes();
        if (!(paramTypeBindings.length == 1
                && (ServiceReference.class.getName().equals(paramTypeBindings[0].getErasure().getQualifiedName())
                        || serviceType == null || serviceType.isAssignmentCompatible(paramTypeBindings[0])))
                && !(paramTypeBindings.length == 2
                        && (serviceType == null || serviceType.isAssignmentCompatible(paramTypeBindings[0]))
                        && Map.class.getName().equals(paramTypeBindings[1].getErasure().getQualifiedName()))) {
            String[] params = new String[paramTypeBindings.length];
            StringBuilder buf = new StringBuilder(64);
            buf.append('(');
            for (int i = 0; i < params.length; ++i) {
                params[i] = paramTypeBindings[i].getName();
                if (buf.length() > 1)
                    buf.append(", "); //$NON-NLS-1$

                buf.append(params[i]);
            }

            buf.append(')');
            reportProblem(annotation, null, problems,
                    NLS.bind(Messages.AnnotationProcessor_invalidBindMethodParameters, buf,
                            serviceType == null ? Messages.AnnotationProcessor_unknownServiceTypeLabel
                                    : serviceType.getName()),
                    params);
        }
    }

    private void validateReferenceTarget(Annotation annotation, String target,
            Collection<DSAnnotationProblem> problems) {
        if (errorLevel.isIgnore())
            return;

        try {
            FrameworkUtil.createFilter(target);
        } catch (InvalidSyntaxException e) {
            String msg = e.getMessage();
            String suffix = ": " + e.getFilter(); //$NON-NLS-1$
            if (msg.endsWith(suffix))
                msg = msg.substring(0, msg.length() - suffix.length());

            reportProblem(annotation, "target", problems, msg, target); //$NON-NLS-1$
        }
    }

    private IMethodBinding findUnbindMethod(ITypeBinding componentClass, ITypeBinding serviceType, String name,
            boolean recurse) {
        ITypeBinding testedClass = componentClass;

        IMethodBinding candidate = null;
        int priority = 0;
        // priority:
        // 0: <assignment-compatible-type>, Map
        // 1: <exact-type>, Map
        // 2: <assignment-compatible-type>
        // 3: <exact-type>
        do {
            for (IMethodBinding declaredMethod : testedClass.getDeclaredMethods()) {
                if (name.equals(declaredMethod.getName())
                        && Void.TYPE.getName().equals(declaredMethod.getReturnType().getName())
                        && (testedClass == componentClass || Modifier.isPublic(declaredMethod.getModifiers())
                                || Modifier.isProtected(declaredMethod.getModifiers())
                                || (!Modifier.isPrivate(declaredMethod.getModifiers())
                                        && testedClass.getPackage().isEqualTo(componentClass.getPackage())))) {
                    ITypeBinding[] paramTypes = declaredMethod.getParameterTypes();
                    if (paramTypes.length == 1) {
                        if (ServiceReference.class.getName().equals(paramTypes[0].getErasure().getQualifiedName()))
                            // we have the winner
                            return declaredMethod;

                        if (priority < 3 && serviceType != null && serviceType.isEqualTo(paramTypes[0]))
                            priority = 3;
                        else if (priority < 2 && serviceType != null
                                && serviceType.isAssignmentCompatible(paramTypes[0]))
                            priority = 2;
                        else
                            continue;

                        // we have a (better) candidate
                        candidate = declaredMethod;
                    } else if (paramTypes.length == 2) {
                        if (priority < 1 && serviceType != null && serviceType.isEqualTo(paramTypes[0])
                                && Map.class.getName().equals(paramTypes[1].getErasure().getQualifiedName()))
                            priority = 1;
                        else if (candidate != null
                                || !(serviceType != null && serviceType.isAssignmentCompatible(paramTypes[0]))
                                || !Map.class.getName().equals(paramTypes[1].getErasure().getQualifiedName()))
                            continue;

                        // we have a candidate
                        candidate = declaredMethod;
                    }
                }
            }
        } while (recurse && (testedClass = testedClass.getSuperclass()) != null);

        return candidate;
    }

    private IMethodBinding findUpdatedMethod(ITypeBinding componentClass, String name, boolean recurse) {
        ITypeBinding testedClass = componentClass;

        IMethodBinding candidate = null;
        do {
            for (IMethodBinding declaredMethod : testedClass.getDeclaredMethods()) {
                if (name.equals(declaredMethod.getName())
                        && Void.TYPE.getName().equals(declaredMethod.getReturnType().getName())
                        && (testedClass == componentClass || Modifier.isPublic(declaredMethod.getModifiers())
                                || Modifier.isProtected(declaredMethod.getModifiers())
                                || (!Modifier.isPrivate(declaredMethod.getModifiers())
                                        && testedClass.getPackage().isEqualTo(componentClass.getPackage())))) {
                    ITypeBinding[] paramTypes = declaredMethod.getParameterTypes();
                    if (paramTypes.length == 1) {
                        if (ServiceReference.class.getName().equals(paramTypes[0].getErasure().getQualifiedName()))
                            // we have the winner
                            return declaredMethod;

                        if (candidate == null
                                && Map.class.getName().equals(paramTypes[0].getErasure().getQualifiedName())) {
                            // we have a candidate
                            candidate = declaredMethod;
                        }
                    }
                }
            }
        } while (recurse && (testedClass = testedClass.getSuperclass()) != null);

        return candidate;
    }

    private void reportProblem(Annotation annotation, String member, Collection<DSAnnotationProblem> problems,
            String message, String... args) {
        reportProblem(annotation, member, -1, problems, message, args);
    }

    private void reportProblem(Annotation annotation, String member, ValidationErrorLevel errorLevel,
            Collection<DSAnnotationProblem> problems, String message, String... args) {
        reportProblem(annotation, member, -1, errorLevel, problems, message, args);
    }

    private void reportProblem(Annotation annotation, String member, int valueIndex,
            Collection<DSAnnotationProblem> problems, String message, String... args) {
        reportProblem(annotation, member, valueIndex, errorLevel, problems, message, args);
    }

    private void reportProblem(Annotation annotation, String member, int valueIndex,
            ValidationErrorLevel errorLevel, Collection<DSAnnotationProblem> problems, String message,
            String... args) {
        if (errorLevel.isIgnore())
            return;

        Expression memberValue = annotation;
        if (annotation.isNormalAnnotation() && member != null) {
            NormalAnnotation na = (NormalAnnotation) annotation;
            for (Object value : na.values()) {
                MemberValuePair pair = (MemberValuePair) value;
                if (member.equals(pair.getName().getIdentifier())) {
                    memberValue = pair.getValue();
                    break;
                }
            }
        } else if (annotation.isSingleMemberAnnotation()) {
            SingleMemberAnnotation sma = (SingleMemberAnnotation) annotation;
            memberValue = sma.getValue();
        }

        int start = memberValue.getStartPosition();
        int length = memberValue.getLength();

        if (valueIndex >= 0 && memberValue instanceof ArrayInitializer) {
            ArrayInitializer ai = (ArrayInitializer) memberValue;
            if (valueIndex < ai.expressions().size()) {
                Expression element = (Expression) ai.expressions().get(valueIndex);
                start = element.getStartPosition();
                length = element.getLength();
            }
        }

        if (start >= 0) {
            DSAnnotationProblem problem = new DSAnnotationProblem(errorLevel.isError(), message, args);
            problem.setSourceStart(start);
            problem.setSourceEnd(start + length - 1);
            problems.add(problem);
        }
    }
}