001package ball.annotation.processing;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: ObjectCloneProcessor.java 7486 2021-02-15 00:02:29Z 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 - 2021 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: 7486 $
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            PROTOTYPE.setAccessible(true);
072        } catch (Exception exception) {
073            throw new ExceptionInInitializerError(exception);
074        }
075    }
076
077    private ExecutableElement METHOD = null;
078    private TypeElement CLONEABLE = null;
079
080    @Override
081    public void init(ProcessingEnvironment processingEnv) {
082        super.init(processingEnv);
083
084        try {
085            METHOD = asExecutableElement(PROTOTYPE);
086            CLONEABLE = asTypeElement(Cloneable.class);
087
088            criteria.add(t -> overrides((ExecutableElement) t, METHOD));
089        } catch (Exception exception) {
090            print(ERROR, exception);
091        }
092    }
093
094    @Override
095    protected void process(RoundEnvironment roundEnv, Element element) {
096        ExecutableElement method = (ExecutableElement) element;
097        TypeElement type = (TypeElement) method.getEnclosingElement();
098
099        if (! type.getInterfaces().contains(CLONEABLE.asType())) {
100            print(WARNING, type,
101                  "%s overrides '%s' but does not implement %s",
102                  type.getKind(),
103                  declaration(PROTOTYPE), CLONEABLE.getSimpleName());
104        }
105
106        if (! types.isAssignable(method.getReturnType(), type.asType())) {
107            print(WARNING, method,
108                  "%s overrides '%s' but does not return a subclass of %s",
109                  method.getKind(),
110                  declaration(PROTOTYPE), type.getSimpleName());
111        }
112
113        List<TypeMirror> throwables =
114            METHOD.getThrownTypes().stream().collect(toList());
115
116        throwables.retainAll(overrides(method).getThrownTypes());
117        throwables.removeAll(method.getThrownTypes());
118        throwables.stream()
119            .map(t -> types.asElement(t))
120            .map(t -> t.getSimpleName())
121            .forEach(t -> print(WARNING, method,
122                                "%s overrides '%s' but does not throw %s",
123                                method.getKind(), declaration(PROTOTYPE), t));
124    }
125}