edu.mit.streamjit.util.bytecode.Klass.java Source code

Java tutorial

Introduction

Here is the source code for edu.mit.streamjit.util.bytecode.Klass.java

Source

/*
 * Copyright (c) 2013-2014 Massachusetts Institute of Technology
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package edu.mit.streamjit.util.bytecode;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import static com.google.common.base.Preconditions.*;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.AbstractSequentialIterator;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.primitives.Shorts;
import edu.mit.streamjit.util.bytecode.types.MethodType;
import edu.mit.streamjit.util.bytecode.types.RegularType;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.Set;

/**
 * A Klass the the IR node representing a class, interface or primitive type;
 * basically, it's a symbolic representation of a Class object.
 * (The name "Class" was already taken.)
 * @author Jeffrey Bosboom <jbosboom@csail.mit.edu>
 * @since 4/3/2013
 */
public final class Klass implements Accessible, Parented<Module> {
    @IntrusiveList.Next
    private Klass next;
    @IntrusiveList.Previous
    private Klass previous;
    @ParentedList.Parent
    private Module parent;
    private final String name;
    private final Set<Modifier> modifiers;
    private final Klass superclass;
    private final List<Klass> interfaces;
    /**
     * For Klasses with a backing Class, lazily initialized on first use.
     */
    private List<Field> fields;
    /**
     * For Klasses with a backing Class, lazily initialized on first use.
     */
    private List<Method> methods;
    /**
     * If this Klass represents a live Class, it's here.  Null otherwise.
     */
    private final Class<?> backingClass;

    /**
     * Creates a new mutable Klass instance.
     * @param name
     * @param superclass
     * @param interfaces
     * @param module
     * @deprecated Prefer the constructor taking modifiers, to avoid forgetting
     * to set PUBLIC or FINAL as appropriate.
     */
    @Deprecated
    public Klass(String name, Klass superclass, List<Klass> interfaces, Module module) {
        this(name, superclass, interfaces, EnumSet.noneOf(Modifier.class), module);
    }

    /**
     * Creates a new mutable Klass instance.
     * @param name
     * @param superclass
     * @param interfaces
     * @param modifiers
     * @param module
     */
    public Klass(String name, Klass superclass, List<Klass> interfaces, Set<Modifier> modifiers, Module module) {
        checkNotNull(name);
        checkNotNull(module);
        checkState(module.getKlass(name) == null, "klass named %s already in module", name);
        this.name = name;
        //EnumSet.copyOf throws on empty non-EnumSets, so we use addAll instead
        this.modifiers = EnumSet.noneOf(Modifier.class);
        modifiers().addAll(modifiers);
        this.superclass = superclass;
        this.interfaces = interfaces == null ? new ArrayList<Klass>(2) : new ArrayList<>(interfaces);
        this.fields = new ParentedList<>(this, Field.class);
        this.methods = new ParentedList<>(this, Method.class);
        this.backingClass = null;
        module.klasses().add(this); //sets parent
    }

    /**
     * Creates a new immutable Klass instance representing the given class,
     * recursively creating new Klass instances as required to resolve
     * references from the given class.
     * @param klass
     * @param module
     * @return
     */
    public Klass(Class<?> klass, Module module) {
        checkNotNull(klass);
        checkNotNull(module);
        checkArgument(module.getKlass(klass.getName()) == null, "klass named %s already in module",
                klass.getName());

        this.backingClass = klass;
        this.name = klass.getName();
        this.modifiers = Sets.immutableEnumSet(Modifier.fromClassBits(Shorts.checkedCast(klass.getModifiers())));

        //We're committed now.  Even through we aren't fully constructed,
        //register us with the module so any circular dependencies can find us.
        //Note that this means we can't use any of the klasses we recurse for
        //during our own construction!
        module.klasses().add(this); //sets parent
        if (klass.getSuperclass() != null)
            this.superclass = module.getKlass(klass.getSuperclass());
        else if (klass.isInterface())
            //At the JVM level, interfaces have Object as superclass.
            this.superclass = module.getKlass(Object.class);
        else {
            assert klass.equals(Object.class) || klass.isPrimitive();
            this.superclass = null;
        }
        ImmutableList.Builder<Klass> interfacesB = ImmutableList.builder();
        for (Class<?> c : klass.getInterfaces())
            interfacesB.add(module.getKlass(c));
        this.interfaces = interfacesB.build();
    }

    private void lazyInitFields() {
        ParentedList<Klass, Field> fieldList = new ParentedList<>(this, Field.class);
        for (java.lang.reflect.Field f : getBackingClass().getDeclaredFields())
            fieldList.add(new Field(f, this, getParent()));
        this.fields = Collections.unmodifiableList(fieldList);
    }

    private void lazyInitMethods() {
        ParentedList<Klass, Method> methodList = new ParentedList<>(this, Method.class);
        for (java.lang.reflect.Constructor<?> c : getBackingClass().getDeclaredConstructors())
            methodList.add(new Method(c, this));
        for (java.lang.reflect.Method m : getBackingClass().getDeclaredMethods())
            methodList.add(new Method(m, this));
        this.methods = Collections.unmodifiableList(methodList);
    }

    /**
     * Creates the array class with the given component type and (additional)
     * dimensions.
     * @param componentType
     * @param dimensions
     * @param module
     */
    public Klass(Klass componentType, int dimensions, Module module) {
        checkNotNull(componentType);
        checkArgument(dimensions >= 1);
        checkNotNull(module);
        StringBuilder nameBuilder = new StringBuilder(Strings.repeat("[", dimensions));
        //Always a reference type; if not already an array, add L and ;.
        nameBuilder.append(componentType.isArray() ? componentType.getName() : "L" + componentType.getName() + ";");
        this.name = nameBuilder.toString();
        checkArgument(module.getKlass(name) == null, "array klass %s already in module", name);
        module.klasses().add(this); //sets parent
        //The access modifier for an array class is that of its element type.
        //Even if the element type is mutable, we only check the modifier once.
        this.modifiers = ImmutableSet.<Modifier>builder().addAll(componentType.getAccess().modifiers())
                .add(Modifier.ABSTRACT).add(Modifier.FINAL).build();
        this.superclass = module.getKlass(Object.class);
        this.interfaces = ImmutableList.of(module.getKlass(Cloneable.class), module.getKlass(Serializable.class));
        this.fields = ImmutableList.of();
        this.methods = ImmutableList.of();
        this.backingClass = null;
    }

    /**
     * Returns true iff this Klass is mutable.  Klasses created from a Class
     * object are immutable.  Klasses representing array classes, even arrays of
     * mutable Klasses, are immutable.
     * @return true iff this Klass is mutable
     */
    public boolean isMutable() {
        return getBackingClass() == null && !isArray();
    }

    /**
     * If this Klass represents a class defined in a live Class object, returns
     * that Class object.  Otherwise, returns null.
     * @return the backing Class of this Klass, or null
     */
    public Class<?> getBackingClass() {
        return backingClass;
    }

    public String getName() {
        return name;
    }

    public Set<Modifier> modifiers() {
        return modifiers;
    }

    public Klass getSuperclass() {
        return superclass;
    }

    public List<Klass> interfaces() {
        return interfaces;
    }

    public List<Field> fields() {
        if (fields == null)
            lazyInitFields();
        return fields;
    }

    public List<Method> methods() {
        if (methods == null)
            lazyInitMethods();
        return methods;
    }

    /**
     * Returns an iterable of all superclasses of this class, in ascending
     * order; thus, the first class is the immediate superclass.
     * @return an an iterable of all superclasses of this class
     */
    public Iterable<Klass> superclasses() {
        return new Iterable<Klass>() {
            @Override
            public Iterator<Klass> iterator() {
                return new AbstractSequentialIterator<Klass>(getSuperclass()) {
                    @Override
                    protected Klass computeNext(Klass previous) {
                        return previous.getSuperclass();
                    }
                };
            }
        };
    }

    public Field getField(String name) {
        for (Field f : fields())
            if (f.getName().equals(name))
                return f;
        return null;
    }

    public Method getMethod(String name, MethodType type) {
        for (Method m : methods())
            if (m.getName().equals(name) && (m.getType().equals(type) || m.isSignaturePolymorphic()))
                return m;
        return null;
    }

    public Iterable<Method> getMethods(final String name) {
        return FluentIterable.from(methods()).filter(new Predicate<Method>() {
            @Override
            public boolean apply(Method input) {
                return input.getName().equals(name);
            }
        });
    }

    /**
     * Looks up the method with the given name and type as though performing
     * virtual dispatch.  That is, if this class does not contain the given
     * method, it will be searched for in superclasses, with the required
     * adjustment to the receiver type.
     * @param name the name of the method to look up
     * @param type the type of the method to look up, including the receiver as
     * the first argument
     * @return the method that would be called by virtual dispatch, or null if
     * no method matches
     */
    public Method getMethodByVirtual(String name, MethodType type) {
        /* TODO: this doesn't correctly enforce the visibility rules for
         * overrides -- see JVMS 5.4.5. */
        //We require an exact match on the non-receiver args.
        MethodType descriptorType = type.dropFirstArgument();
        RegularType receiver = type.getParameterTypes().get(0);
        for (Klass k = this; k != null; k = k.getSuperclass()) {
            for (Method m : k.getMethods(name)) {
                if (m == null || m.modifiers().contains(Modifier.STATIC))
                    continue;
                if (m.isSignaturePolymorphic()) //accepts any method type
                    return m;
                MethodType desc = m.getType().dropFirstArgument();
                RegularType rec = m.getType().getParameterTypes().get(0);
                if (desc.equals(descriptorType) && receiver.isSubtypeOf(rec))
                    return m;
            }
        }
        //If we can't find a match in a class, try interfaces.
        Queue<Klass> interfaceWorklist = new ArrayDeque<>(interfaces());
        while (!interfaceWorklist.isEmpty()) {
            Klass k = interfaceWorklist.remove();
            for (Method m : k.getMethods(name)) {
                if (m == null || m.modifiers().contains(Modifier.STATIC))
                    continue;
                MethodType desc = m.getType().dropFirstArgument();
                RegularType rec = m.getType().getParameterTypes().get(0);
                if (desc.equals(descriptorType) && receiver.isSubtypeOf(rec))
                    return m;
            }
            interfaceWorklist.addAll(k.interfaces());
        }
        return null;
    }

    @Override
    public Access getAccess() {
        return Access.fromModifiers(modifiers());
    }

    @Override
    public void setAccess(Access access) {
        modifiers().removeAll(Access.allAccessModifiers());
        modifiers().addAll(access.modifiers());
    }

    public final boolean isArray() {
        return getName().startsWith("[");
    }

    /**
     * If this Klass represents an array class, returns the number of array
     * dimensions; otherwise, returns 0.
     * @return the number of dimensions, or 0
     */
    public final int getDimensions() {
        return getName().lastIndexOf('[') + 1;
    }

    /**
     * Returns the component Klass of this array class; that is, the Klass with
     * one fewer dimension (possibly with zero dimensions).  Thus [[I becomes
     * [I.
     * @return the component Klass of this array class
     * @throws IllegalStateException if this Klass is not an array class
     */
    public final Klass getComponentKlass() {
        checkState(isArray(), "not array class: %s", getName());
        int nd = getDimensions() - 1;
        if (nd == 0)
            return getElementKlass();
        else
            return getParent().getArrayKlass(getElementKlass(), getDimensions() - 1);
    }

    /**
     * Returns the element Klass of this array class; that is, the Klass with
     * all dimensions stripped off.  Thus [[I becomes I.
     * @return the element Klass of this array class
     * @throws IllegalStateException if this Klass is not an array class
     */
    public final Klass getElementKlass() {
        checkState(isArray(), "not array class: %s", getName());
        if (getBackingClass() != null) {
            Class<?> b = getBackingClass();
            while (b.getComponentType() != null)
                b = b.getComponentType();
            return getParent().getKlass(b);
        }
        //Our element type is mutable, so we must have already created it.
        String elementName = getName().replaceAll("\\[*L(.*);", "$1");
        Klass elementKlass = getParent().getKlass(elementName);
        assert elementKlass != null;
        return elementKlass;
    }

    @Override
    public final Module getParent() {
        return parent;
    }

    @Override
    public String toString() {
        return getName();
    }

    public void dump(OutputStream stream) {
        dump(new PrintWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8)));
    }

    public void dump(Writer writer) {
        dump(new PrintWriter(writer));
    }

    public void dump(PrintWriter writer) {
        writer.write(Joiner.on(' ').join(modifiers()));
        writer.write(" ");
        writer.write(getName());
        writer.println();
        if (getSuperclass() != null) {
            writer.write('\t');
            writer.write("extends ");
            writer.write(Joiner.on(", ").join(superclasses()));
            writer.println();
        }
        if (!interfaces().isEmpty()) {
            writer.write('\t');
            writer.write("implements ");
            writer.write(
                    Joiner.on(", ").join(FluentIterable.from(interfaces()).transform(new Function<Klass, String>() {
                        @Override
                        public String apply(Klass input) {
                            return input.getName();
                        }
                    })));
            writer.println();
        }
        //TODO: "indirectly implements"
        writer.println();

        for (Field f : fields())
            writer.println(f.toString());
        writer.println();

        for (Method m : methods()) {
            m.dump(writer);
            writer.println();
        }

        writer.flush();
    }

    public static void main(String[] args) {
        Module m = new Module();
        System.out.println(new Klass(ParentedList.class, m));
        m.getKlass(ParentedList.class).dump(new PrintWriter(System.out, true));
        System.out.println(new Klass(ImmutableList.class, m));
        System.out.println(new Klass(javax.swing.JCheckBoxMenuItem.class, m));
        System.out.println(new Klass(java.nio.file.Files.class, m));
        System.out.println(new Klass(javax.net.ssl.SSLSocket.class, m));
        System.out.println(m.klasses().size() + " classes reflectively parsed");
        System.out.println(m.constants().getNullConstant());
        System.out.println(m.constants().getSmallestIntConstant(-255));
    }
}