/*
 * LensKit, an open source recommender systems toolkit.
 * Copyright 2010-2014 LensKit Contributors.  See CONTRIBUTORS.md.
 * Work on LensKit has been funded by the National Science Foundation under
 * grants IIS 05-34939, 08-08692, 08-12148, and 10-17697.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.grouplens.lenskit.core;

import org.grouplens.grapht.annotation.AliasFor;

import javax.annotation.processing.*;
import javax.inject.Qualifier;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.util.Set;

/**
 * Annotation processor to provide basic linting of LensKit annotations.
 *
 * @see Shareable
 * @see Parameter
 */
@SupportedAnnotationTypes("org.grouplens.lenskit.core.*")
public class LenskitAnnotationProcessor extends AbstractProcessor {
    public LenskitAnnotationProcessor() {}

    private Types typeUtils;
    private Elements elementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        note("LensKit Shareable linting active");
        typeUtils = processingEnv.getTypeUtils();
        elementUtils = processingEnv.getElementUtils();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        // support version 6 or 7
        // we can't compile against RELEASE_7 and maintain Java 6 compatibility, but the
        // processor is Java 7-compatible. We have not tested against Java 8, however.
        SourceVersion[] versions = SourceVersion.values();
        SourceVersion v6 = SourceVersion.RELEASE_6;
        assert v6.ordinal() < versions.length;
        // we support up through Java 8
        return versions[Math.min(v6.ordinal() + 2, versions.length - 1)];
    }

    private Messager getLog() {
        return processingEnv.getMessager();
    }

    private void note(String fmt, Object... args) {
        return; // do nothing to avoid being noisy
//        String msg = String.format(fmt, args);
//        getLog().printMessage(Diagnostic.Kind.NOTE, msg);
    }

    private void error(String fmt, Object... args) {
        String msg = String.format(fmt, args);
        getLog().printMessage(Diagnostic.Kind.ERROR, msg);
    }

    private void warning(Element e, String fmt, Object... args) {
        String msg = String.format(fmt, args);
        if (e == null) {
            getLog().printMessage(Diagnostic.Kind.MANDATORY_WARNING, msg);
        } else {
            getLog().printMessage(Diagnostic.Kind.MANDATORY_WARNING, msg, e);
        }
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        checkShareableComponents(roundEnv);
        checkParameters(roundEnv);
        return false;
    }

    /**
     * Check shareable components for serializability.
     * @param round The round environment.
     */
    private void checkShareableComponents(RoundEnvironment round) {
        Set<? extends Element> elts = round.getElementsAnnotatedWith(Shareable.class);
        note("processing %d shareable elements", elts.size());
        TypeMirror serializable = elementUtils.getTypeElement("java.io.Serializable").asType();
        for (Element elt: elts) {
            note("examining %s", elt);
            TypeMirror type = elt.asType();
            if (typeUtils.isAssignable(type, serializable)) {
                note("shareable type %s is serializable", type);
            } else {
                warning(elt, "shareable type %s is not serializable", type);
            }
        }
    }

    /**
     * Check parameter annotations for being qualifiers.
     * @param round The round environment.
     */
    private void checkParameters(RoundEnvironment round) {
        Set<? extends Element> params = round.getElementsAnnotatedWith(Parameter.class);
        note("processing %d parameter annotations", params.size());
        for (Element param: params) {
            Qualifier q = param.getAnnotation(Qualifier.class);
            if (q == null) {
                warning(param, "parameter %s is not annotated as a qualifier", param);
            }
            AliasFor alias = param.getAnnotation(AliasFor.class);
            if (alias != null) {
                warning(param, "parameter %s is an alias (@Parameter should be on target)", param);
            }
        }
    }
}

@@ -4147,16 +4147,66 @@
 , elt);%0A
+            if (elt instanceof TypeElement) %7B%0A    
         
@@ -4249,24 +4249,28 @@
             
+   
+ 
 if (typeUtil
@@ -4319,24 +4319,28 @@
             
+    
 note(%22sharea
@@ -4380,32 +4380,36 @@
 e);%0A            
+ 
+   
 %7D else %7B%0A       
@@ -4393,32 +4393,36 @@
        %7D else %7B%0A
+    
                 
@@ -4474,32 +4474,151 @@
 izable%22, type);%0A
+                %7D%0A            %7D else %7B%0A                warning(elt, %22shareable checking not supported for non-types%22);%0A
             %7D%0A  

Handle non-type elements in annotation processor