com.sun.tdk.jcov.processing.CombinerDataProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.sun.tdk.jcov.processing.CombinerDataProcessor.java

Source

/*
 * Copyright (c) 2014, 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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.
 */
package com.sun.tdk.jcov.processing;

import com.sun.tdk.jcov.instrument.DataClass;
import com.sun.tdk.jcov.instrument.DataField;
import com.sun.tdk.jcov.instrument.DataMethod;
import com.sun.tdk.jcov.instrument.DataMethodEntryOnly;
import com.sun.tdk.jcov.instrument.DataPackage;
import com.sun.tdk.jcov.instrument.DataRoot;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import org.objectweb.asm.Opcodes;

/**
 * Processor, that combines classes derived from the same sources.
 *
 * @author Dmitry Fazunenko
 */
public class CombinerDataProcessor implements DataProcessor {

    public DataRoot process(DataRoot root) throws ProcessingException {
        HashMap<String, ArrayList<DataClass>> classesMap = new HashMap<String, ArrayList<DataClass>>();
        DataRoot result = new DataRoot(root.getParams());
        result.setScaleOpts(root.getScaleOpts());

        // building map: scr --> cls1, cls2, ...
        List<DataPackage> allPkg = root.getPackages();
        for (DataPackage pkg : allPkg) {
            for (Iterator<DataClass> it = pkg.getClasses().iterator(); it.hasNext();) {
                DataClass cls = it.next();
                String srcName = cls.getSource();
                if (srcName != null) {
                    String key = pkg.getName() + '/' + srcName;
                    ArrayList<DataClass> srcClasses = classesMap.get(key);
                    if (srcClasses == null) {
                        srcClasses = new ArrayList<DataClass>();
                        classesMap.put(key, srcClasses);
                    }
                    srcClasses.add(cls);
                } else {
                    result.addClass(cls); // src == null
                }
            }
        }

        // combining classes derived from the same source
        for (String key : classesMap.keySet()) {
            ArrayList<DataClass> toMerge = classesMap.get(key);
            if (toMerge.size() == 1) {
                // nothing to combine: one class in the source
                result.addClass(toMerge.get(0)); // just one class
            } else {
                int k = key.indexOf(".");
                String mainClassName = k > 0 ? key.substring(0, k) : key;
                DataClass cls = findMainClazz(toMerge, mainClassName);
                // it's expected, the main class is always the first in the list
                DataClass newClass = cls.clone(result.rootId());

                k = mainClassName.lastIndexOf("/");
                if (k > 0) {
                    mainClassName = mainClassName.substring(k + 1);
                }

                for (DataClass c : toMerge) {
                    if (c == cls) {
                        continue; // skip mainClass
                    }

                    boolean isPublic = isPublic(c, toMerge);
                    String clzName = c.getName();
                    int j = clzName.lastIndexOf("/");
                    if (j >= 0) {
                        clzName = clzName.substring(j + 1);
                    }
                    String prefix = createPrefix(clzName, mainClassName);
                    for (DataMethod m : c.getMethods()) {
                        int newAccess = isPublic ? m.getAccess() : makePrivate(m.getAccess());

                        DataMethod nm = m.clone(newClass, newAccess, prefix + m.getName());
                        // for -type=method methods exist witout blocks and branches
                        if (nm instanceof DataMethodEntryOnly) {
                            nm.setCount(m.getCount());
                        }
                        // new created method will be added to the newClass
                    }
                    for (DataField f : c.getFields()) {
                        int newAccess = isPublic ? f.getAccess() : makePrivate(f.getAccess());
                        f.clone(newClass, newAccess, prefix + f.getName());
                        // new created field will be added to the newClass
                    }
                }
                result.addClass(newClass);
            }
        }
        return result;

    }

    /**
     * Finds the main class for the sources which name equals to the source
     *
     * @param toMerge
     * @param mainClassName
     * @return
     */
    protected DataClass findMainClazz(ArrayList<DataClass> toMerge, String mainClassName) {
        for (DataClass c : toMerge) {
            if (mainClassName.equals(c.getFullname())) {
                return c;
            }
        }
        return toMerge.get(0);
    }

    /**
     * Forms the prefix for a given class to be used in the report. This
     * implementation uses empty prefix for the main class, and a class name
     * precided with the "!" for nested classes, and precided with "%" for
     * classes defined outside the main one.
     *
     * This method can be overrided to alter the rules above.
     *
     * @param className - class name to generate prefix for
     * @param mainClassName - the main class of the source
     * @return prefix
     */
    protected String createPrefix(String className, String mainClassName) {
        if (className.equals(mainClassName)) {
            return "";
        } else if (className.startsWith(mainClassName + "$")) {
            return className.substring(className.indexOf("$")) + ".";
        } else {
            return "~" + className + ".";
        }
    }

    /**
     * Returns true if the given class is public (contains "public" modifier)
     * and all its outers are public as well.
     *
     * @param cls - class to analyze
     * @param peers classes the could be potentionaly outer of the cls
     * @return true or false
     */
    private boolean isPublic(DataClass cls, ArrayList<DataClass> peers) {
        ArrayList<DataClass> outers = findOuters(cls, peers);
        DataClass outClass = outers.get(outers.size() - 1);
        for (DataClass c : outers) {
            if (!isPublic(c, outClass)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Scans given modifiers in attempt to find "public".
     *
     * @param modifiers - array to scan
     * @return true if given array is not null and contains "public"
     */
    private boolean isPublic(DataClass c, DataClass outClass) {
        if (isAnonymous(c.getName())) {
            return isPublicAnonymous(c, outClass);
        }

        return (c.getAccess() & Opcodes.ACC_PUBLIC) != 0;
    }

    private boolean isPublicAnonymous(DataClass c, DataClass outClass) {

        TreeSet<DataMethod> sortedMethods = new TreeSet<DataMethod>(new Comparator<DataMethod>() {
            @Override
            public int compare(DataMethod dm1, DataMethod dm2) {
                return dm1.getLineTable().get(dm1.getLineTable().size() - 1).line
                        - dm2.getLineTable().get(dm2.getLineTable().size() - 1).line;
            }
        });

        for (DataMethod dm : outClass.getMethods()) {
            if (dm.getLineTable() != null) {
                sortedMethods.add(dm);
            }
        }

        DataMethod initMethod = c.findMethod("<init>");

        if (sortedMethods != null && initMethod != null) {
            for (DataMethod dataMethod : sortedMethods) {

                if (initMethod.getLineTable().get(0).line <= dataMethod.getLineTable()
                        .get(dataMethod.getLineTable().size() - 1).line) {

                    //Anonymous classes in init and clinit is not public
                    if (dataMethod.getName().equals("<init>") || dataMethod.getName().equals("<clinit>")) {
                        return false;
                    }

                    return dataMethod.isPublicAPI();
                }

            }
        }

        return false;
    }

    /**
     * Finds outer classes among classes obtained from the same source.
     *
     * @param cls - class to find outers
     * @param peers - class obtained from the same source
     * @return list of outers including cls itself.
     */
    private ArrayList<DataClass> findOuters(DataClass cls, ArrayList<DataClass> peers) {
        ArrayList<DataClass> result = new ArrayList<DataClass>();
        result.add(cls);
        String name = cls.getName();
        for (DataClass c : peers) {
            if (name.startsWith(c.getName() + "$")) {
                result.add(c);
            }
        }
        return result;
    }

    /**
     * Removes "public" and "protected" modifiers from the given list
     *
     * @param modifiers array of modifiers
     * @return modified array
     */
    private int makePrivate(int modifiers) {
        return modifiers & ~Opcodes.ACC_PUBLIC & ~Opcodes.ACC_PROTECTED;
    }

    /**
     * @param name - name of a class
     * @return true, if the given class name is anonyomous
     */
    private boolean isAnonymous(String name) {
        if (name == null) {
            return false;
        }
        int index = name.lastIndexOf("$");
        if (index < 0) {
            return false;
        }
        return Character.isDigit(name.charAt(index + 1));
    }
}