/*
 * Copyright (c) 2007, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.glassfish.gmbal.typelib ;

import java.util.Collections ;
import java.util.List ;
import java.util.ArrayList ;

import org.glassfish.pfl.basic.algorithm.Graph;
import org.glassfish.pfl.basic.func.UnaryPredicate;

    
Analyzes class inheritance hiearchy and provides methods for searching for classes and methods.
/** Analyzes class inheritance hiearchy and provides methods for searching for * classes and methods. */
public class EvaluatedClassAnalyzer { // General purpose class analyzer // // The basic problem is to determine for any class its linearized inheritance // sequence. This is an old problem in OOP. For my purpose, I want the following // to be true: // // Let C be a class, let C.super be C's superclass, and let C.inter be the list of // C's implemented interfaces (C may be an interface, abstract, or concrete class). // Define ILIST(C) to be a sequence that satisfies the following properties: // // 1. ILIST(C) starts with C. // 2. If X is in ILIST(C), then so is X.super and each element of X.inter. // 3. For any class X in ILIST(C): // 2a. X appears before X.super in ILIST(C) // 2b. X appears before any X.inter in ILIST(C) // 4. No class appears more than once in ILIST(C) // // Note that the order can change when new classes are analyzed, so each class must be // analyzed independently // // We need to elaborate on this idea to handle several issues: // // 1. We start with needing to determine whether a particular class C is ManagedData (mapped // to composite data, and used for attribute and operation values in an Open MBean) or // ManagedObject (mapped to an MBean with an ObjectName). We will require that the super // class graph of any object contain at most one class annotated with @ManagedObject or // @ManagedData (and not both). This means that for any class C, there is a class MC // (which may be C) which is the unique class that is a superclass of C and is annotated // with either @ManagedData or @ManagedObject. // 2. The MC class may also contain InheritedAttribute and IncludeSubclass annotations. // InheritedAttribute is handled by searching in the superclasses for getter and setters // conforming to the InheritedAttribute id. IncludeSubclass extends the set of classes // to scan for @ManagedAttribute and @ManagedOperation by the union of MC's superclasses, // and the superclasses of all classes specified by IncludeSubclass. // 3. What we require here is that ALL classes that share the same MC class translate to the // SAME kind of MBean or CompositeData. // // An additional complexity is that we need to properly handle generic Types. // The basic problem is that a reference to Map<K,V> tells us nothing about how // to map keys and values until we know to what K and V are bound. Some cases // are pretty simple: Map<String,Foo> where Foo is @ManagedData is easy. The // complexity arises in things like // @ManagedData // class Manager<T> { // @ManagedAttribute // Map<String,T> info() ; // } // // Then later we see a reference to Manager<Foo>, from which we need to // determine that T is bound to Foo (from the ParameterizedType.getActualTypeAguments() // method). // // To do this, we need to evaluate all types as follows (in terms of typelib): // 1. A ClassDeclaration is evaluated to a ParameterizedType with base=ClassDeclaration // (but with all types reachable from the ClassDecl evaluated) and empty // typeAssignment. // 2. A TypeVariable evaluates to whatever it is bound to, or to the union // of its upper bounds, or to Object (as a ParameterizedType). // 3. A GenericArrayType evaluates to a GenericArrayType of the evaluation // of the component type. // 4. A WildcardType evaluates to the union of its upper bounds, or to // Object if it has no upper bounds (alt. could evaluate to intersection // of lower bounds, but that's probably object anyway). // 5. A ParameterizedType evaluates to a ParameterizedType in which the // base type and all of the Types in the TypeAssignment are fully evaluated. // // In the end, the only types that remain are types in the set EvalType defined // as follows: // 1. ParameterizedType in which base and all elements of typeAssignment are // in EvalType. // 2. GenericArrayType in which componentType is in EvalType. // 3. ClassDeclaration in which all elements of inheritance() and all types // in methods.returnType and methods.parameterTypes where method is from // methods() are in EvalType. // // Organization. // // While we probably could do the construction and analysis in one pass, // it's probably better NOT to do this, at least initially. Instead, we will: // // 1. Construct a simple Graph just as we have been. // 2. Use a visitor to walk the Graph, converting native-mode (DeclarationFactory) // typelib objects into fully-evaluated constructed-mode (DeclarationConstructor) // typelib objects. // 3. Cache all of this. // // Construction of the visitor // // The visitor needs to maintain a Display<TypeVariable,Type> to use in // constructing the evaluated graph. We need to enter a new scope whenever // the visitor references a ParameterizedType, and exit the scope whenever // the visitor completes processing of the ParameterizedType. The cache is // essential too, as otherwise we could get cycles in the complex recursion. // This also means that all types must be settable after construction. private static final Graph.Finder<EvaluatedClassDeclaration> finder = new Graph.Finder<EvaluatedClassDeclaration>() { public List<EvaluatedClassDeclaration> evaluate( EvaluatedClassDeclaration arg ) { return arg.inheritance() ; } } ; private final List<EvaluatedClassDeclaration> classInheritance ; private String contents = null ; private EvaluatedClassAnalyzer( Graph<EvaluatedClassDeclaration> gr ) { List<EvaluatedClassDeclaration> result = new ArrayList<EvaluatedClassDeclaration>( gr.getPostorderList() ) ; Collections.reverse( result ) ; classInheritance = result ; } public EvaluatedClassAnalyzer( final EvaluatedClassDeclaration cls ) { this( new Graph<EvaluatedClassDeclaration>( cls, finder ) ) ; } public EvaluatedClassAnalyzer( final List<EvaluatedClassDeclaration> decls ) { this( new Graph<EvaluatedClassDeclaration>( decls, finder ) ) ; } public List<EvaluatedClassDeclaration> findClasses( UnaryPredicate<EvaluatedClassDeclaration> pred ) { final List<EvaluatedClassDeclaration> result = new ArrayList<EvaluatedClassDeclaration>() ; for (EvaluatedClassDeclaration c : classInheritance) { if (pred.evaluate( c )) { result.add( c ) ; } } return result ; } // Tested by testFindMethod // Tested by testGetAnnotatedMethods public List<EvaluatedMethodDeclaration> findMethods( UnaryPredicate<EvaluatedMethodDeclaration> pred ) { final List<EvaluatedMethodDeclaration> result = new ArrayList<EvaluatedMethodDeclaration>() ; for (EvaluatedClassDeclaration c : classInheritance) { for (EvaluatedMethodDeclaration m : c.methods()) { if (pred.evaluate( m )) { result.add( m ) ; } } } return result ; } public List<EvaluatedFieldDeclaration> findFields( UnaryPredicate<EvaluatedFieldDeclaration> pred ) { final List<EvaluatedFieldDeclaration> result = new ArrayList<EvaluatedFieldDeclaration>() ; for (EvaluatedClassDeclaration c : classInheritance) { for (EvaluatedFieldDeclaration f : c.fields()) { if (pred.evaluate( f )) { result.add( f ) ; } } } return result ; } @Override public synchronized String toString() { if (contents == null) { StringBuilder sb = new StringBuilder() ; boolean first = true ; sb.append( "ClassAnalyzer[" ) ; for (EvaluatedClassDeclaration cls : classInheritance) { if (first) { first = false ; } else { sb.append( " " ) ; } sb.append( cls.name() ) ; } sb.append( "]" ) ; contents = sb.toString() ; } return contents ; } }