/*
 * (C) Copyright Uwe Schindler (Generics Policeman) and others.
 * Parts of this work are licensed to the Apache Software Foundation (ASF)
 * under one or more contributor license agreements.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.thetaphi.forbiddenapis;

import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;

/** Utility class that is used to get an overview of all fields and implemented
 * methods of a class. It make the signatures available as Sets. */
final class ClassSignature implements Constants {
  private ClassReader reader;
  
  public final boolean isRuntimeClass;
  public final Set<Method> methods;
  public final Set<String> fields, signaturePolymorphicMethods;
	public final String className, superName;
	public final String[] interfaces;
  
	/** Builds the information from an ASM ClassReader */
  public ClassSignature(final ClassReader classReader, boolean isRuntimeClass, boolean withReader) {
    this.reader = withReader ? classReader : null;
    this.isRuntimeClass = isRuntimeClass;
    this.className = classReader.getClassName();
    this.superName = classReader.getSuperName();
    this.interfaces = classReader.getInterfaces();
    final Set<Method> methods = new HashSet<Method>();
    final Set<String> fields = new HashSet<String>();
    final Set<String> signaturePolymorphicMethods = new HashSet<String>();
    classReader.accept(new ClassVisitor(Opcodes.ASM5) {
      @Override
      public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        final Method m = new Method(name, desc);
        methods.add(m);
        if (className.startsWith(SIGNATURE_POLYMORPHIC_PKG_INTERNALNAME) &&
            (access & Opcodes.ACC_VARARGS) != 0 &&
            (access & Opcodes.ACC_NATIVE) != 0 &&
            SIGNATURE_POLYMORPHIC_DESCRIPTOR.equals(desc)
        ) {
          signaturePolymorphicMethods.add(name);
        }
        return null;
      }
      
      @Override
      public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        fields.add(name);
        return null;
      }
    }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
    this.methods = createSet(methods);
    this.fields = createSet(fields);
    this.signaturePolymorphicMethods = createSet(signaturePolymorphicMethods);
  }

  /** Alternative ctor that can be used to build the information via reflection from an already loaded class. Useful for Java 9 Jigsaw. */
  public ClassSignature(final Class<?> clazz, boolean isRuntimeClass) {
    this.reader = null; // no reader available!
    this.isRuntimeClass = isRuntimeClass;
    this.className = Type.getType(clazz).getInternalName();
    final Class<?> superclazz = clazz.getSuperclass();
    this.superName = superclazz == null ? null : Type.getType(superclazz).getInternalName();
    final Class<?>[] interfClasses = clazz.getInterfaces();
    this.interfaces = new String[interfClasses.length];
    for (int i = 0; i < interfClasses.length; i++) {
      this.interfaces[i] = Type.getType(interfClasses[i]).getInternalName();
    }
    final Set<Method> methods = new HashSet<Method>();
    final Set<String> fields = new HashSet<String>();
    final Set<String> signaturePolymorphicMethods = new HashSet<String>();
    for (final java.lang.reflect.Method m : clazz.getDeclaredMethods()) {
      methods.add(Method.getMethod(m));
      if (className.startsWith(SIGNATURE_POLYMORPHIC_PKG_INTERNALNAME) &&
          m.isVarArgs() &&
          (m.getModifiers() & Modifier.NATIVE) != 0 &&
          SIGNATURE_POLYMORPHIC_DESCRIPTOR.equals(Type.getMethodDescriptor(m))
      ) {
        signaturePolymorphicMethods.add(m.getName());
      }
    }
    for (final java.lang.reflect.Constructor<?> m : clazz.getDeclaredConstructors()) {
      methods.add(Method.getMethod(m));
    }
    for (final java.lang.reflect.Field f : clazz.getDeclaredFields()) {
      fields.add(f.getName());
    }
    this.methods = createSet(methods);
    this.fields = createSet(fields);
    this.signaturePolymorphicMethods = createSet(signaturePolymorphicMethods);
  }
  
  private static <T> Set<T> createSet(Set<? extends T> s) {
    return s.isEmpty() ? Collections.<T>emptySet() : Collections.<T>unmodifiableSet(s);
  }

  public ClassReader getReader() {
    if (reader == null)
      throw new IllegalStateException("'" + Type.getObjectType(className).getClassName() + "' has no ClassReader, because it was already checked or is only loaded as related class.");
    try {
      return reader;
    } finally {
      reader = null;
    }
  }
}

@@ -1530,17 +1530,18 @@
 ethods;%0A
-%09
+  
 public f
@@ -1574,17 +1574,18 @@
 erName;%0A
-%09
+  
 public f
@@ -1617,9 +1617,10 @@
 %0A  %0A
-%09
+  
 /** 

Fix tabs