org.eclipselabs.nullness.NullnessCompiler.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipselabs.nullness.NullnessCompiler.java

Source

/*******************************************************************************
 * Copyright (c) 2012 Sebastian Zarnekow (http://zarnekow.blogspot.de) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Authors:
 *   Sebastian Zarnekow - Initial implementation
 *******************************************************************************/
package org.eclipselabs.nullness;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IRegion;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.BuildContext;
import org.eclipse.jdt.core.compiler.CompilationParticipant;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipselabs.nullness.equinox.JavaProjectAnnotationSet;
import org.eclipselabs.nullness.equinox.MergedAnnotationSet;
import org.eclipselabs.nullness.equinox.NullnessAnnotationSet;
import org.eclipselabs.nullness.equinox.RegistryAnnotationSet;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.EmptyVisitor;

public class NullnessCompiler extends CompilationParticipant {

    private static final Logger log = Logger.getLogger(NullnessCompiler.class);

    private List<BuildContext> files;

    private final NullnessAnnotationSet[] defaultAnnotationSets;

    private NullnessAnnotationSet projectAnnotationSet;

    public NullnessCompiler() {
        this.defaultAnnotationSets = RegistryAnnotationSet.getAnnotationSets();
    }

    @Override
    public int aboutToBuild(IJavaProject project) {
        projectAnnotationSet = new JavaProjectAnnotationSet(project);
        return super.aboutToBuild(project);
    }

    @Override
    public void buildStarting(BuildContext[] files, boolean isBatch) {
        if (this.files != null) {
            this.files.addAll(Arrays.asList(files));
        } else {
            this.files = new ArrayList<BuildContext>(Arrays.asList(files));
        }
    }

    @Override
    public boolean isActive(IJavaProject project) {
        Boolean result = new PreferencesAccessor().getBoolean(PreferencesAccessor.ACTIVE_BOOLEAN,
                project.getProject());
        if (result != null) {
            return result.booleanValue();
        }
        return false;
    }

    @Override
    public void buildFinished(IJavaProject project) {
        try {
            super.buildFinished(project);
            if (files != null) {
                NullAnnotationFinder finder = new NullAnnotationFinder(
                        new MergedAnnotationSet(projectAnnotationSet, defaultAnnotationSets));
                FieldCache fieldCache = new FieldCache(project, finder);
                for (BuildContext file : files) {
                    try {
                        addRuntimeChecks(file.getFile(), finder, fieldCache);
                    } catch (JavaModelException e) {
                        NullnessCompiler.log.error(e.getMessage(), e);
                    }
                }
            }
        } finally {
            files = null;
            projectAnnotationSet = null;
        }
    }

    private void addRuntimeChecks(IFile javaFile, NullAnnotationFinder finder, FieldCache fieldCache)
            throws JavaModelException {
        IRegion region = JavaCore.newRegion();
        ICompilationUnit cu = (ICompilationUnit) JavaCore.create(javaFile);
        region.add(cu);
        IResource[] resources = JavaCore.getGeneratedResources(region, false);
        ASTParser parser = ASTParser.newParser(AST.JLS4);
        parser.setProject(cu.getJavaProject());
        parser.setIgnoreMethodBodies(false);
        IType[] allTypes = getAllTypes(cu);
        IBinding[] bindings = parser.createBindings(allTypes, null);
        for (IResource resource : resources) {
            if (resource instanceof IFile) {
                IFile file = (IFile) resource;
                if ("class".equals(file.getFileExtension())) {
                    try {
                        final InputStream inputStream = file.getContents();
                        try {
                            ClassReader reader = new ClassReader(inputStream);
                            int version = getClassFileVersion(reader);
                            if (version >= Opcodes.V1_5) {
                                ClassWriter writer = new ClassWriter(getClassWriterFlags(version));
                                String binaryName = file.getFullPath().removeFileExtension().lastSegment();
                                ITypeBinding typeBinding = findTypeBinding(binaryName, bindings);
                                if (typeBinding != null) {
                                    final NullnessAssertionInserter nullChecker = new NullnessAssertionInserter(
                                            writer, typeBinding, finder, fieldCache);
                                    reader.accept(nullChecker, 0);
                                    if (nullChecker.isCheckInserted()) {
                                        ByteArrayInputStream newContent = new ByteArrayInputStream(
                                                writer.toByteArray());
                                        file.setContents(newContent, IResource.NONE, null);
                                    }
                                }
                            }
                        } finally {
                            inputStream.close();
                        }
                    } catch (CoreException e) {
                        // TODO reasonable exception handling
                        throw new RuntimeException(e);
                    } catch (IOException e) {
                        // TODO reasonable exception handling
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    private IType[] getAllTypes(ICompilationUnit compilationUnit) throws JavaModelException {
        List<IType> result = new ArrayList<IType>(4);
        for (IType type : compilationUnit.getTypes()) {
            result.add(type);
            addAllMemberTypes(type, result);
        }
        return result.toArray(new IType[result.size()]);
    }

    private void addAllMemberTypes(IMember container, List<IType> result) throws JavaModelException {
        for (IJavaElement child : container.getChildren()) {
            if (child instanceof IMember) {
                if (child instanceof IType) {
                    result.add((IType) child);
                }
                addAllMemberTypes((IMember) child, result);
            }
        }
    }

    private ITypeBinding findTypeBinding(String binaryClassName, IBinding[] bindings) {
        for (IBinding binding : bindings) {
            if (binding instanceof ITypeBinding) {
                ITypeBinding typeBinding = (ITypeBinding) binding;
                String candidate = typeBinding.getBinaryName();
                if (candidate == null) {
                    IType javaType = (IType) typeBinding.getJavaElement();
                    candidate = javaType.getTypeQualifiedName('$');
                }
                if (candidate != null) {
                    if (candidate.length() == binaryClassName.length()) {
                        if (candidate.equals(binaryClassName)) {
                            return typeBinding;
                        }
                    } else if (candidate.endsWith(binaryClassName)
                            && candidate.charAt(candidate.length() - binaryClassName.length() - 1) == '.') {
                        return typeBinding;
                    }
                }
            }
        }
        return null;
    }

    private int getClassFileVersion(ClassReader reader) {
        // TODO get this from the Java project
        final int[] classfileVersion = new int[1];
        reader.accept(new EmptyVisitor() {
            @Override
            public void visit(int version, int access, String name, String signature, String superName,
                    String[] interfaces) {
                classfileVersion[0] = version;
            }
        }, 0);

        return classfileVersion[0];
    }

    private int getClassWriterFlags(int version) {
        return version >= Opcodes.V1_6 && version != Opcodes.V1_1 ? ClassWriter.COMPUTE_FRAMES
                : ClassWriter.COMPUTE_MAXS;
    }

}