/*
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/*
 * @test
 * @bug 8029891
 * @summary Test that the Properties class overrides all public+protected
 *          methods of all ancestor classes and interfaces
 * @run main CheckOverrides
 */
public class CheckOverrides {

    public static void main(String[] args) {
        Set<MethodSignature> pMethodSignatures =
            Stream.of(Properties.class.getDeclaredMethods())
                .filter(CheckOverrides::isMethodOfInterest)
                .map(MethodSignature::new)
                .collect(Collectors.toSet());

        Map<MethodSignature, Method> unoverriddenMethods = new HashMap<>();
        for (Class<?> superclass = Properties.class.getSuperclass();
             superclass != Object.class;
             superclass = superclass.getSuperclass()) {
            Stream.of(superclass.getDeclaredMethods())
                .filter(CheckOverrides::isMethodOfInterest)
                .forEach(m -> unoverriddenMethods.putIfAbsent(new MethodSignature(m), m));
        }
        unoverriddenMethods.keySet().removeAll(pMethodSignatures);

        if (!unoverriddenMethods.isEmpty()) {
            throw new RuntimeException(
                "The following methods should be overridden by Properties class:\n" +
                    unoverriddenMethods.values().stream()
                        .map(Method::toString)
                        .collect(Collectors.joining("\n  ", "  ", "\n"))
            );
        }
    }

    static boolean isMethodOfInterest(Method method) {
        int mods = method.getModifiers();
        return !Modifier.isStatic(mods) &&
            (Modifier.isPublic(mods) || Modifier.isProtected(mods));
    }

    static class MethodSignature {
        final Class<?> returnType;
        final String name;
        final Class<?>[] parameterTypes;

        MethodSignature(Method method) {
            this(method.getReturnType(), method.getName(), method.getParameterTypes());
        }

        private MethodSignature(Class<?> returnType, String name, Class<?>[] parameterTypes) {
            this.returnType = returnType;
            this.name = name;
            this.parameterTypes = parameterTypes;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            MethodSignature that = (MethodSignature) o;
            if (!returnType.equals(that.returnType)) return false;
            if (!name.equals(that.name)) return false;
            return Arrays.equals(parameterTypes, that.parameterTypes);
        }

        @Override
        public int hashCode() {
            int result = returnType.hashCode();
            result = 31 * result + name.hashCode();
            result = 31 * result + Arrays.hashCode(parameterTypes);
            return result;
        }
    }
}