001package ball.annotation.processing;
002/*-
003 * ##########################################################################
004 * Utilities
005 * %%
006 * Copyright (C) 2008 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import ball.annotation.ServiceProviderFor;
022import java.lang.reflect.Method;
023import java.util.Objects;
024import java.util.ResourceBundle;
025import java.util.Set;
026import javax.annotation.processing.ProcessingEnvironment;
027import javax.annotation.processing.Processor;
028import javax.annotation.processing.RoundEnvironment;
029import javax.lang.model.element.AnnotationMirror;
030import javax.lang.model.element.Element;
031import javax.lang.model.element.ExecutableElement;
032import javax.lang.model.element.TypeElement;
033import javax.lang.model.type.TypeMirror;
034import lombok.NoArgsConstructor;
035import lombok.ToString;
036
037import static javax.lang.model.element.ElementKind.CLASS;
038import static javax.lang.model.element.Modifier.ABSTRACT;
039import static javax.lang.model.type.TypeKind.NONE;
040import static javax.tools.Diagnostic.Kind.ERROR;
041import static javax.tools.Diagnostic.Kind.WARNING;
042
043/**
044 * {@link Processor} implementation to check {@link Class}es to verify:
045 * <ol>
046 *   <li value="1">
047 *     The implementing {@link Class} also overrides {@link Object#toString()}
048 *   </li>
049 * </ol>
050 *
051 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
052 */
053@ServiceProviderFor({ Processor.class })
054@ForElementKinds({ CLASS })
055@ForSubclassesOf(Object.class)
056@WithoutModifiers({ ABSTRACT })
057@NoArgsConstructor @ToString
058public class ObjectToStringProcessor extends AnnotatedNoAnnotationProcessor {
059    private static final Method PROTOTYPE;
060
061    static {
062        try {
063            PROTOTYPE = Object.class.getDeclaredMethod("toString");
064            PROTOTYPE.setAccessible(true);
065        } catch (Exception exception) {
066            throw new ExceptionInInitializerError(exception);
067        }
068    }
069
070    private static final Set<String> ANNOTATIONS =
071        ResourceBundle.getBundle(ObjectToStringProcessor.class.getName())
072        .keySet();
073
074    private ExecutableElement METHOD = null;
075
076    @Override
077    public void init(ProcessingEnvironment processingEnv) {
078        super.init(processingEnv);
079
080        try {
081            METHOD = asExecutableElement(PROTOTYPE);
082        } catch (Exception exception) {
083            print(ERROR, exception);
084        }
085    }
086
087    @Override
088    protected void process(RoundEnvironment roundEnv, Element element) {
089        if (! isGenerated(element)) {
090            TypeElement type = (TypeElement) element;
091
092            if (! isAnnotated(type)) {
093                ExecutableElement implementation = implementationOf(METHOD, type);
094
095                if (implementation == null || METHOD.equals(implementation)) {
096                    print(WARNING, type, "%s does not override '%s'", type.getKind(), declaration(PROTOTYPE));
097                }
098            }
099        }
100    }
101
102    private boolean isAnnotated(TypeElement element) {
103        boolean found =
104            element.getAnnotationMirrors().stream()
105            .map(AnnotationMirror::getAnnotationType)
106            .map(Objects::toString)
107            .anyMatch(ANNOTATIONS::contains);
108
109        if (! found) {
110            TypeMirror mirror = element.getSuperclass();
111
112            if (mirror != null && (! mirror.getKind().equals(NONE))) {
113                found |= isAnnotated((TypeElement) types.asElement(mirror));
114            }
115        }
116
117        return found;
118    }
119}