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.List;
024import javax.annotation.processing.ProcessingEnvironment;
025import javax.annotation.processing.Processor;
026import javax.annotation.processing.RoundEnvironment;
027import javax.lang.model.element.Element;
028import javax.lang.model.element.ExecutableElement;
029import javax.lang.model.element.TypeElement;
030import javax.lang.model.type.TypeMirror;
031import lombok.NoArgsConstructor;
032import lombok.ToString;
033
034import static java.util.stream.Collectors.toList;
035import static javax.lang.model.element.ElementKind.METHOD;
036import static javax.lang.model.element.Modifier.PRIVATE;
037import static javax.lang.model.element.Modifier.STATIC;
038import static javax.tools.Diagnostic.Kind.ERROR;
039import static javax.tools.Diagnostic.Kind.WARNING;
040
041/**
042 * {@link Processor} implementation to check {@link Object#clone()}
043 * implementations to verify:
044 * <ol>
045 *   <li value="1">
046 *     The implementing {@link Class} also implements {@link Cloneable}
047 *   </li>
048 *   <li value="2">
049 *     The implementation throws {@link CloneNotSupportedException} (unless
050 *     some "intravening" superclass' implementation does not)
051 *   </li>
052 *   <li value="3">
053 *     The implementation returns a subtype of the implementation type
054 *   </li>
055 * </ol>
056 *
057 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
058 */
059@ServiceProviderFor({ Processor.class })
060@ForElementKinds({ METHOD })
061@NoArgsConstructor @ToString
062public class ObjectCloneProcessor extends AnnotatedNoAnnotationProcessor {
063    private static final Method PROTOTYPE;
064
065    static {
066        try {
067            PROTOTYPE = Object.class.getDeclaredMethod("clone");
068            PROTOTYPE.setAccessible(true);
069        } catch (Exception exception) {
070            throw new ExceptionInInitializerError(exception);
071        }
072    }
073
074    private ExecutableElement METHOD = null;
075    private TypeElement CLONEABLE = null;
076
077    @Override
078    public void init(ProcessingEnvironment processingEnv) {
079        super.init(processingEnv);
080
081        try {
082            METHOD = asExecutableElement(PROTOTYPE);
083            CLONEABLE = asTypeElement(Cloneable.class);
084
085            criteria.add(t -> overrides((ExecutableElement) t, METHOD));
086        } catch (Exception exception) {
087            print(ERROR, exception);
088        }
089    }
090
091    @Override
092    protected void process(RoundEnvironment roundEnv, Element element) {
093        if (! isGenerated(element)) {
094            ExecutableElement method = (ExecutableElement) element;
095            TypeElement type = (TypeElement) method.getEnclosingElement();
096
097            if (! type.getInterfaces().contains(CLONEABLE.asType())) {
098                print(WARNING, type,
099                      "%s overrides '%s' but does not implement %s",
100                      type.getKind(), declaration(PROTOTYPE), CLONEABLE.getSimpleName());
101            }
102
103            if (! types.isAssignable(method.getReturnType(), type.asType())) {
104                print(WARNING, method,
105                      "%s overrides '%s' but does not return a subclass of %s",
106                      method.getKind(), declaration(PROTOTYPE), type.getSimpleName());
107            }
108
109            List<TypeMirror> throwables = METHOD.getThrownTypes().stream().collect(toList());
110
111            throwables.retainAll(overrides(method).getThrownTypes());
112            throwables.removeAll(method.getThrownTypes());
113            throwables.stream()
114                .map(t -> types.asElement(t))
115                .map(t -> t.getSimpleName())
116                .forEach(t -> print(WARNING, method,
117                                    "%s overrides '%s' but does not throw %s",
118                                    method.getKind(), declaration(PROTOTYPE), t));
119        }
120    }
121}