Package com.google.devtools.depan.java.bytecode.impl

Source Code of com.google.devtools.depan.java.bytecode.impl.ClassDepLister

/*
* Copyright 2007 Google Inc.
*
* 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 com.google.devtools.depan.java.bytecode.impl;

import java.io.File;

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 com.google.devtools.depan.filesystem.graph.FileElement;
import com.google.devtools.depan.java.graph.FieldElement;
import com.google.devtools.depan.java.graph.InterfaceElement;
import com.google.devtools.depan.java.graph.JavaRelation;
import com.google.devtools.depan.java.graph.MethodElement;
import com.google.devtools.depan.java.graph.PackageElement;
import com.google.devtools.depan.java.graph.TypeElement;
import com.google.devtools.depan.model.builder.DependenciesListener;

/**
* Implements a visitor of the ASM package, to find the dependencies in a class
* file and build the dependency tree. A single {@link ClassDepLister} is used
* for each file found in the explored directory or jar file.
*
* To build the dependencies tree, it calls the methods of a
* {@link DependenciesListener}.
*
* @author ycoppel@google.com (Yohann Coppel)
*/
public class ClassDepLister extends ClassVisitor {

  /**
   * {@link DependenciesListener} called when a dependency is found.
   */
  private final DependenciesListener dl;

  /**
   * File element for source code.
   */
  private final FileElement fileNode;

  /**
   * class currently read class. (typically the class A when the file A.java is
   * read.
   */
  private TypeElement mainClass = null;


  /**
   * constructor for new {@link ClassDepLister}.
   *
   * @param dl {@link DependenciesListener} implementing callbacks
   * @param fileNode node for the .class file containing this class
   */
  public ClassDepLister(DependenciesListener dl, FileElement fileNode) {
    super(Opcodes.ASM4);
    this.dl = dl;
    this.fileNode = fileNode;
  }

  @Override
  public void visit(int version, int access, String name, String signature,
      String superName, String[] interfaces) {
    mainClass = TypeNameUtil.fromInternalName(name);

    PackageElement packageNode = installPackageForTypeName(name);
    dl.newDep(packageNode, mainClass, JavaRelation.CLASS);

    dl.newDep(TypeNameUtil.fromInternalName(superName), mainClass,
        JavaRelation.EXTENDS);
    for (String s : interfaces) {
      InterfaceElement element = TypeNameUtil.fromInterfaceName(s);
      dl.newDep(element, mainClass, JavaRelation.IMPLEMENTS);
    }
    checkAnonymousType(name);
  }

  /**
   * Check if the given internal name is an anonymous class. If so, generate
   * the appropriate dependencies.
   *
   * @param name
   */
  private void checkAnonymousType(String name) {
    // anonymous classes names contains a $ followed by a digit
    if (name.contains("$")) {
      String superClass = name.substring(0, name.lastIndexOf('$'));

      // recursively check: maybe name is not an anonymous class, but is
      // an innerclass contained into an anonymous class.
      checkAnonymousType(superClass);

      // A digit must follow the $ in the name.
      if (Character.isDigit(name.charAt(name.lastIndexOf('$')+1))) {
        TypeElement superType = TypeNameUtil.fromInternalName(superClass);
        dl.newDep(superType, mainClass, JavaRelation.ANONYMOUS_TYPE);
      }
    }
  }

  @Override
  public FieldVisitor visitField(int access, String name, String desc,
      String signature, Object value) {
    TypeElement type = TypeNameUtil.fromDescriptor(desc);
    FieldElement field = new FieldElement(name, type, mainClass);

    // simple className
    dl.newDep(mainClass, type, JavaRelation.TYPE);

    // field
    JavaRelation r = null;
    if ((Opcodes.ACC_STATIC & access) != 0) {
      r = JavaRelation.STATIC_FIELD;
    } else {
      r = JavaRelation.MEMBER_FIELD;
    }
    dl.newDep(mainClass, field, r);

    // generic types in signature
    // TODO(ycoppel): how to get types in generics ? (signature)

    return FieldDepLister.LISTER;
  }

  @Override
  public void visitInnerClass(String name, String outerName, String innerName,
      int access) {
    if ((null == outerName) || (null == innerName)) {
      //System.out.println("visitInnerClass()" + name + " - "
      //    + outerName + " - " + innerName + " - " + access + " @ "
      //    + mainClass);
      // FIXME(ycoppel): probably an enum. What to do ?
      return;
    }
    TypeElement inner = TypeNameUtil.fromInternalName(name);
    if (inner.equals(mainClass)) {
      // the visitInnerClass callback is called twice: once when visiting the
      // outer class (A in A$B), and once when visiting the A$B class. we
      // shortcut the second case so we don't add the dependency twice.
      return;
    }
    TypeElement parent = TypeNameUtil.fromInternalName(outerName);
    dl.newDep(parent, inner, JavaRelation.INNER_TYPE);
  }

  @Override
  public MethodVisitor visitMethod(int access, String name, String desc,
      String signature, String[] exceptions) {

    // the method itself
    MethodElement m = new MethodElement(desc, name, mainClass);

    JavaRelation r = null;
    if ((Opcodes.ACC_STATIC & access) != 0) {
      r = JavaRelation.STATIC_METHOD;
    } else {
      r = JavaRelation.MEMBER_METHOD;
    }
    dl.newDep(mainClass, m, r);

    // arguments dependencies
    for (Type t : Type.getArgumentTypes(desc)) {
      dl.newDep(m, TypeNameUtil.fromDescriptor(t.getDescriptor()),
          JavaRelation.TYPE);
    }

    // return-type dependency
    TypeElement type = TypeNameUtil.fromDescriptor(
        Type.getReturnType(desc).getDescriptor());
    dl.newDep(m, type, JavaRelation.READ);

    return new MethodDepLister(dl, m);
  }

  @Override
  public void visitOuterClass(String owner, String name, String desc) {
    // nothing to do. We use the visitInnerClass callback instead.
  }

  /**
   * Create a source element using the full path-name of the source file.
   * If the mainClass is not a top-level class, assume it is an inner
   * class and its immediate container is not a source file.
   */
  @Override
  public void visitSource(String source, String debug) {
    if (mainClass.getFullyQualifiedName().contains("$")) {
      // this is an inner class. We use the visitInnerClass() callback instead.
      return;
    }

    // Link the main class to it's containing file
    dl.newDep(fileNode, mainClass, JavaRelation.CLASSFILE);
  }

  /**
   * Install a package hierarchy and a matching directory hierarchy for
   * the full path name of the type.
   *
   * @param typePath full path name of a type
   * @return PackageElement that contains the type
   */
  private PackageElement installPackageForTypeName(String typePath) {
    // TODO(leeca): Add short-circuit early exit if package is already defined.
    // This would avoid a fair bit of unnecessary object creation.
    File packageFile = new File(typePath).getParentFile();
    if (null == packageFile) {
      return new PackageElement("<unnamed>");
    }
    File treeFile = createTreeFile();
    PackageTreeBuilder packageBuilder = new PackageTreeBuilder(dl);

    return packageBuilder.installPackageTree(packageFile, treeFile);
  }

  /**
   * Define the tree for the file node, even if it doesn't have a directory.
   * This can happen for class files at the top of the analysis tree, such as
   * classes in the unnamed package at the top of a Jar file.
   *
   * @return valid directory tree reference
   */
  private File createTreeFile() {
    File result = new File(fileNode.getPath()).getParentFile();
    if (null == result) {
      return new File("");
    }
    return result;
  }
}
TOP

Related Classes of com.google.devtools.depan.java.bytecode.impl.ClassDepLister

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.