001package ball.annotation.processing;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: ObjectCloneProcessor.java 5896 2020-05-08 18:25:01Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-util/trunk/src/main/java/ball/annotation/processing/ObjectCloneProcessor.java $
007 * %%
008 * Copyright (C) 2008 - 2020 Allen D. Ball
009 * %%
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 *
014 *      http://www.apache.org/licenses/LICENSE-2.0
015 *
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 * ##########################################################################
022 */
023import ball.annotation.ServiceProviderFor;
024import java.lang.reflect.Method;
025import java.util.List;
026import javax.annotation.processing.ProcessingEnvironment;
027import javax.annotation.processing.Processor;
028import javax.annotation.processing.RoundEnvironment;
029import javax.lang.model.element.Element;
030import javax.lang.model.element.ExecutableElement;
031import javax.lang.model.element.TypeElement;
032import javax.lang.model.type.TypeMirror;
033import lombok.NoArgsConstructor;
034import lombok.ToString;
035
036import static java.util.stream.Collectors.toList;
037import static javax.lang.model.element.ElementKind.METHOD;
038import static javax.lang.model.element.Modifier.PRIVATE;
039import static javax.lang.model.element.Modifier.STATIC;
040import static javax.tools.Diagnostic.Kind.ERROR;
041import static javax.tools.Diagnostic.Kind.WARNING;
042
043/**
044 * {@link Processor} implementation to check {@link Object#clone()}
045 * implementations to verify:
046 * <ol>
047 *   <li value="1">
048 *     The implementing {@link Class} also implements {@link Cloneable}
049 *   </li>
050 *   <li value="2">
051 *     The implementation throws {@link CloneNotSupportedException} (unless
052 *     some "intravening" superclass' implementation does not)
053 *   </li>
054 *   <li value="3">
055 *     The implementation returns a subtype of the implementation type
056 *   </li>
057 * </ol>
058 *
059 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
060 * @version $Revision: 5896 $
061 */
062@ServiceProviderFor({ Processor.class })
063@ForElementKinds({ METHOD })
064@NoArgsConstructor @ToString
065public class ObjectCloneProcessor extends AnnotatedNoAnnotationProcessor {
066    private static final Method PROTOTYPE;
067
068    static {
069        try {
070            PROTOTYPE = Object.class.getDeclaredMethod("clone");
071        } catch (Exception exception) {
072            throw new ExceptionInInitializerError(exception);
073        }
074    }
075
076    private ExecutableElement METHOD = null;
077    private TypeElement CLONEABLE = null;
078
079    @Override
080    public void init(ProcessingEnvironment processingEnv) {
081        super.init(processingEnv);
082
083        try {
084            METHOD = getMethod(PROTOTYPE);
085            CLONEABLE = asTypeElement(Cloneable.class);
086
087            criteria.add(t -> overrides((ExecutableElement) t, METHOD));
088        } catch (Exception exception) {
089            print(ERROR, exception);
090        }
091    }
092
093    @Override
094    protected void process(RoundEnvironment roundEnv, Element element) {
095        ExecutableElement method = (ExecutableElement) element;
096        TypeElement type = (TypeElement) method.getEnclosingElement();
097
098        if (! type.getInterfaces().contains(CLONEABLE.asType())) {
099            print(WARNING, type,
100                  "%s overrides '%s' but does not implement %s",
101                  type.getKind(),
102                  declaration(PROTOTYPE), CLONEABLE.getSimpleName());
103        }
104
105        if (! types.isAssignable(method.getReturnType(), type.asType())) {
106            print(WARNING, method,
107                  "%s overrides '%s' but does not return a subclass of %s",
108                  method.getKind(),
109                  declaration(PROTOTYPE), type.getSimpleName());
110        }
111
112        List<TypeMirror> throwables =
113            METHOD.getThrownTypes().stream().collect(toList());
114
115        throwables.retainAll(overrides(method).getThrownTypes());
116        throwables.removeAll(method.getThrownTypes());
117        throwables.stream()
118            .map(t -> types.asElement(t))
119            .map(t -> t.getSimpleName())
120            .forEach(t -> print(WARNING, method,
121                                "%s overrides '%s' but does not throw %s",
122                                method.getKind(), declaration(PROTOTYPE), t));
123    }
124}