alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (JavacFiler.java)

This example Java source code file (JavacFiler.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

charsequence, fileobject, filerexception, fileroutputfileobject, fileroutputjavafileobject, gen, illegalstateexception, ioexception, javafileobject, linkedhashset, names, override, set, string, util

The JavacFiler.java Java example source code

/*
 * Copyright (c) 2005, 2011, 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.tools.javac.processing;

import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FilterOutputStream;
import java.io.Reader;
import java.io.Writer;
import java.io.FilterWriter;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.*;

import static java.util.Collections.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Element;
import javax.tools.*;
import javax.tools.JavaFileManager.Location;

import static javax.tools.StandardLocation.SOURCE_OUTPUT;
import static javax.tools.StandardLocation.CLASS_OUTPUT;

import com.sun.tools.javac.code.Lint;
import com.sun.tools.javac.util.*;

import static com.sun.tools.javac.code.Lint.LintCategory.PROCESSING;

/**
 * The FilerImplementation class must maintain a number of
 * constraints.  First, multiple attempts to open the same path within
 * the same invocation of the tool results in an IOException being
 * thrown.  For example, trying to open the same source file twice:
 *
 * <pre>
 * createSourceFile("foo.Bar")
 * ...
 * createSourceFile("foo.Bar")
 * </pre>
 *
 * is disallowed as is opening a text file that happens to have
 * the same name as a source file:
 *
 * <pre>
 * createSourceFile("foo.Bar")
 * ...
 * createTextFile(SOURCE_TREE, "foo", new File("Bar"), null)
 * </pre>
 *
 * <p>Additionally, creating a source file that corresponds to an
 * already created class file (or vice versa) also results in an
 * IOException since each type can only be created once.  However, if
 * the Filer is used to create a text file named *.java that happens
 * to correspond to an existing class file, a warning is *not*
 * generated.  Similarly, a warning is not generated for a binary file
 * named *.class and an existing source file.
 *
 * <p>The reason for this difference is that source files and class
 * files are registered with the tool and can get passed on as
 * declarations to the next round of processing.  Files that are just
 * named *.java and *.class are not processed in that manner; although
 * having extra source files and class files on the source path and
 * class path can alter the behavior of the tool and any final
 * compile.
 *
 * <p>This is NOT part of any supported API.
 * If you write code that depends on this, you do so at your own risk.
 * This code and its internal interfaces are subject to change or
 * deletion without notice.</b>
 */
public class JavacFiler implements Filer, Closeable {
    // TODO: Implement different transaction model for updating the
    // Filer's record keeping on file close.

    private static final String ALREADY_OPENED =
        "Output stream or writer has already been opened.";
    private static final String NOT_FOR_READING =
        "FileObject was not opened for reading.";
    private static final String NOT_FOR_WRITING =
        "FileObject was not opened for writing.";

    /**
     * Wrap a JavaFileObject to manage writing by the Filer.
     */
    private class FilerOutputFileObject extends ForwardingFileObject<FileObject> {
        private boolean opened = false;
        private String name;

        FilerOutputFileObject(String name, FileObject fileObject) {
            super(fileObject);
            this.name = name;
        }

        @Override
        public synchronized OutputStream openOutputStream() throws IOException {
            if (opened)
                throw new IOException(ALREADY_OPENED);
            opened = true;
            return new FilerOutputStream(name, fileObject);
        }

        @Override
        public synchronized Writer openWriter() throws IOException {
            if (opened)
                throw new IOException(ALREADY_OPENED);
            opened = true;
            return new FilerWriter(name, fileObject);
        }

        // Three anti-literacy methods
        @Override
        public InputStream openInputStream() throws IOException {
            throw new IllegalStateException(NOT_FOR_READING);
        }

        @Override
        public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
            throw new IllegalStateException(NOT_FOR_READING);
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            throw new IllegalStateException(NOT_FOR_READING);
        }

        @Override
        public boolean delete() {
            return false;
        }
    }

    private class FilerOutputJavaFileObject extends FilerOutputFileObject implements JavaFileObject {
        private final JavaFileObject javaFileObject;
        FilerOutputJavaFileObject(String name, JavaFileObject javaFileObject) {
            super(name, javaFileObject);
            this.javaFileObject = javaFileObject;
        }

        public JavaFileObject.Kind getKind() {
            return javaFileObject.getKind();
        }

        public boolean isNameCompatible(String simpleName,
                                        JavaFileObject.Kind kind) {
            return javaFileObject.isNameCompatible(simpleName, kind);
        }

        public NestingKind getNestingKind() {
            return javaFileObject.getNestingKind();
        }

        public Modifier getAccessLevel() {
            return javaFileObject.getAccessLevel();
        }
    }

    /**
     * Wrap a JavaFileObject to manage reading by the Filer.
     */
    private class FilerInputFileObject extends ForwardingFileObject<FileObject> {
        FilerInputFileObject(FileObject fileObject) {
            super(fileObject);
        }

        @Override
        public OutputStream openOutputStream() throws IOException {
            throw new IllegalStateException(NOT_FOR_WRITING);
        }

        @Override
        public Writer openWriter() throws IOException {
            throw new IllegalStateException(NOT_FOR_WRITING);
        }

        @Override
        public boolean delete() {
            return false;
        }
    }

    private class FilerInputJavaFileObject extends FilerInputFileObject implements JavaFileObject {
        private final JavaFileObject javaFileObject;
        FilerInputJavaFileObject(JavaFileObject javaFileObject) {
            super(javaFileObject);
            this.javaFileObject = javaFileObject;
        }

        public JavaFileObject.Kind getKind() {
            return javaFileObject.getKind();
        }

        public boolean isNameCompatible(String simpleName,
                                        JavaFileObject.Kind kind) {
            return javaFileObject.isNameCompatible(simpleName, kind);
        }

        public NestingKind getNestingKind() {
            return javaFileObject.getNestingKind();
        }

        public Modifier getAccessLevel() {
            return javaFileObject.getAccessLevel();
        }
    }


    /**
     * Wrap a {@code OutputStream} returned from the {@code
     * JavaFileManager} to properly register source or class files
     * when they are closed.
     */
    private class FilerOutputStream extends FilterOutputStream {
        String typeName;
        FileObject fileObject;
        boolean closed = false;

        /**
         * @param typeName name of class or {@code null} if just a
         * binary file
         */
        FilerOutputStream(String typeName, FileObject fileObject) throws IOException {
            super(fileObject.openOutputStream());
            this.typeName = typeName;
            this.fileObject = fileObject;
        }

        public synchronized void close() throws IOException {
            if (!closed) {
                closed = true;
                /*
                 * If an IOException occurs when closing the underlying
                 * stream, still try to process the file.
                 */

                closeFileObject(typeName, fileObject);
                out.close();
            }
        }
    }

    /**
     * Wrap a {@code Writer} returned from the {@code JavaFileManager}
     * to properly register source or class files when they are
     * closed.
     */
    private class FilerWriter extends FilterWriter {
        String typeName;
        FileObject fileObject;
        boolean closed = false;

        /**
         * @param fileObject the fileObject to be written to
         * @param typeName name of source file or {@code null} if just a
         * text file
         */
        FilerWriter(String typeName, FileObject fileObject) throws IOException {
            super(fileObject.openWriter());
            this.typeName = typeName;
            this.fileObject = fileObject;
        }

        public synchronized void close() throws IOException {
            if (!closed) {
                closed = true;
                /*
                 * If an IOException occurs when closing the underlying
                 * Writer, still try to process the file.
                 */

                closeFileObject(typeName, fileObject);
                out.close();
            }
        }
    }

    JavaFileManager fileManager;
    Log log;
    Context context;
    boolean lastRound;

    private final boolean lint;

    /**
     * Logical names of all created files.  This set must be
     * synchronized.
     */
    private final Set<FileObject> fileObjectHistory;

    /**
     * Names of types that have had files created but not closed.
     */
    private final Set<String> openTypeNames;

    /**
     * Names of source files closed in this round.  This set must be
     * synchronized.  Its iterators should preserve insertion order.
     */
    private Set<String> generatedSourceNames;

    /**
     * Names and class files of the class files closed in this round.
     * This set must be synchronized.  Its iterators should preserve
     * insertion order.
     */
    private final Map<String, JavaFileObject> generatedClasses;

    /**
     * JavaFileObjects for source files closed in this round.  This
     * set must be synchronized.  Its iterators should preserve
     * insertion order.
     */
    private Set<JavaFileObject> generatedSourceFileObjects;

    /**
     * Names of all created source files.  Its iterators should
     * preserve insertion order.
     */
    private final Set<String> aggregateGeneratedSourceNames;

    /**
     * Names of all created class files.  Its iterators should
     * preserve insertion order.
     */
    private final Set<String> aggregateGeneratedClassNames;


    JavacFiler(Context context) {
        this.context = context;
        fileManager = context.get(JavaFileManager.class);

        log = Log.instance(context);

        fileObjectHistory = synchronizedSet(new LinkedHashSet<FileObject>());
        generatedSourceNames = synchronizedSet(new LinkedHashSet<String>());
        generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<JavaFileObject>());

        generatedClasses = synchronizedMap(new LinkedHashMap<String, JavaFileObject>());

        openTypeNames  = synchronizedSet(new LinkedHashSet<String>());

        aggregateGeneratedSourceNames = new LinkedHashSet<String>();
        aggregateGeneratedClassNames  = new LinkedHashSet<String>();

        lint = (Lint.instance(context)).isEnabled(PROCESSING);
    }

    public JavaFileObject createSourceFile(CharSequence name,
                                           Element... originatingElements) throws IOException {
        return createSourceOrClassFile(true, name.toString());
    }

    public JavaFileObject createClassFile(CharSequence name,
                                           Element... originatingElements) throws IOException {
        return createSourceOrClassFile(false, name.toString());
    }

    private JavaFileObject createSourceOrClassFile(boolean isSourceFile, String name) throws IOException {
        if (lint) {
            int periodIndex = name.lastIndexOf(".");
            if (periodIndex != -1) {
                String base = name.substring(periodIndex);
                String extn = (isSourceFile ? ".java" : ".class");
                if (base.equals(extn))
                    log.warning("proc.suspicious.class.name", name, extn);
            }
        }
        checkNameAndExistence(name, isSourceFile);
        Location loc = (isSourceFile ? SOURCE_OUTPUT : CLASS_OUTPUT);
        JavaFileObject.Kind kind = (isSourceFile ?
                                    JavaFileObject.Kind.SOURCE :
                                    JavaFileObject.Kind.CLASS);

        JavaFileObject fileObject =
            fileManager.getJavaFileForOutput(loc, name, kind, null);
        checkFileReopening(fileObject, true);

        if (lastRound)
            log.warning("proc.file.create.last.round", name);

        if (isSourceFile)
            aggregateGeneratedSourceNames.add(name);
        else
            aggregateGeneratedClassNames.add(name);
        openTypeNames.add(name);

        return new FilerOutputJavaFileObject(name, fileObject);
    }

    public FileObject createResource(JavaFileManager.Location location,
                                     CharSequence pkg,
                                     CharSequence relativeName,
                                     Element... originatingElements) throws IOException {
        locationCheck(location);

        String strPkg = pkg.toString();
        if (strPkg.length() > 0)
            checkName(strPkg);

        FileObject fileObject =
            fileManager.getFileForOutput(location, strPkg,
                                         relativeName.toString(), null);
        checkFileReopening(fileObject, true);

        if (fileObject instanceof JavaFileObject)
            return new FilerOutputJavaFileObject(null, (JavaFileObject)fileObject);
        else
            return new FilerOutputFileObject(null, fileObject);
    }

    private void locationCheck(JavaFileManager.Location location) {
        if (location instanceof StandardLocation) {
            StandardLocation stdLoc = (StandardLocation) location;
            if (!stdLoc.isOutputLocation())
                throw new IllegalArgumentException("Resource creation not supported in location " +
                                                   stdLoc);
        }
    }

    public FileObject getResource(JavaFileManager.Location location,
                                  CharSequence pkg,
                                  CharSequence relativeName) throws IOException {
        String strPkg = pkg.toString();
        if (strPkg.length() > 0)
            checkName(strPkg);

        // TODO: Only support reading resources in selected output
        // locations?  Only allow reading of non-source, non-class
        // files from the supported input locations?

        // In the following, getFileForInput is the "obvious" method
        // to use, but it does not have the "obvious" semantics for
        // SOURCE_OUTPUT and CLASS_OUTPUT. Conversely, getFileForOutput
        // does not have the correct semantics for any "path" location
        // with more than one component. So, for now, we use a hybrid
        // invocation.
        FileObject fileObject;
        if (location.isOutputLocation()) {
            fileObject = fileManager.getFileForOutput(location,
                    pkg.toString(),
                    relativeName.toString(),
                    null);
        } else {
            fileObject = fileManager.getFileForInput(location,
                    pkg.toString(),
                    relativeName.toString());
        }
        if (fileObject == null) {
            String name = (pkg.length() == 0)
                    ? relativeName.toString() : (pkg + "/" + relativeName);
            throw new FileNotFoundException(name);
        }

        // If the path was already opened for writing, throw an exception.
        checkFileReopening(fileObject, false);
        return new FilerInputFileObject(fileObject);
    }

    private void checkName(String name) throws FilerException {
        checkName(name, false);
    }

    private void checkName(String name, boolean allowUnnamedPackageInfo) throws FilerException {
        if (!SourceVersion.isName(name) && !isPackageInfo(name, allowUnnamedPackageInfo)) {
            if (lint)
                log.warning("proc.illegal.file.name", name);
            throw new FilerException("Illegal name " + name);
        }
    }

    private boolean isPackageInfo(String name, boolean allowUnnamedPackageInfo) {
        // Is the name of the form "package-info" or
        // "foo.bar.package-info"?
        final String PKG_INFO = "package-info";
        int periodIndex = name.lastIndexOf(".");
        if (periodIndex == -1) {
            return allowUnnamedPackageInfo ? name.equals(PKG_INFO) : false;
        } else {
            // "foo.bar.package-info." illegal
            String prefix = name.substring(0, periodIndex);
            String simple = name.substring(periodIndex+1);
            return SourceVersion.isName(prefix) && simple.equals(PKG_INFO);
        }
    }

    private void checkNameAndExistence(String typename, boolean allowUnnamedPackageInfo) throws FilerException {
        // TODO: Check if type already exists on source or class path?
        // If so, use warning message key proc.type.already.exists
        checkName(typename, allowUnnamedPackageInfo);
        if (aggregateGeneratedSourceNames.contains(typename) ||
            aggregateGeneratedClassNames.contains(typename)) {
            if (lint)
                log.warning("proc.type.recreate", typename);
            throw new FilerException("Attempt to recreate a file for type " + typename);
        }
    }

    /**
     * Check to see if the file has already been opened; if so, throw
     * an exception, otherwise add it to the set of files.
     */
    private void checkFileReopening(FileObject fileObject, boolean addToHistory) throws FilerException {
        for(FileObject veteran : fileObjectHistory) {
            if (fileManager.isSameFile(veteran, fileObject)) {
                if (lint)
                    log.warning("proc.file.reopening", fileObject.getName());
                throw new FilerException("Attempt to reopen a file for path " + fileObject.getName());
            }
        }
        if (addToHistory)
            fileObjectHistory.add(fileObject);
    }

    public boolean newFiles() {
        return (!generatedSourceNames.isEmpty())
            || (!generatedClasses.isEmpty());
    }

    public Set<String> getGeneratedSourceNames() {
        return generatedSourceNames;
    }

    public Set<JavaFileObject> getGeneratedSourceFileObjects() {
        return generatedSourceFileObjects;
    }

    public Map<String, JavaFileObject> getGeneratedClasses() {
        return generatedClasses;
    }

    public void warnIfUnclosedFiles() {
        if (!openTypeNames.isEmpty())
            log.warning("proc.unclosed.type.files", openTypeNames.toString());
    }

    /**
     * Update internal state for a new round.
     */
    public void newRound(Context context) {
        this.context = context;
        this.log = Log.instance(context);
        clearRoundState();
    }

    void setLastRound(boolean lastRound) {
        this.lastRound = lastRound;
    }

    public void close() {
        clearRoundState();
        // Cross-round state
        fileObjectHistory.clear();
        openTypeNames.clear();
        aggregateGeneratedSourceNames.clear();
        aggregateGeneratedClassNames.clear();
    }

    private void clearRoundState() {
        generatedSourceNames.clear();
        generatedSourceFileObjects.clear();
        generatedClasses.clear();
    }

    /**
     * Debugging function to display internal state.
     */
    public void displayState() {
        PrintWriter xout = context.get(Log.outKey);
        xout.println("File Object History : " +  fileObjectHistory);
        xout.println("Open Type Names     : " +  openTypeNames);
        xout.println("Gen. Src Names      : " +  generatedSourceNames);
        xout.println("Gen. Cls Names      : " +  generatedClasses.keySet());
        xout.println("Agg. Gen. Src Names : " +  aggregateGeneratedSourceNames);
        xout.println("Agg. Gen. Cls Names : " +  aggregateGeneratedClassNames);
    }

    public String toString() {
        return "javac Filer";
    }

    /**
     * Upon close, register files opened by create{Source, Class}File
     * for annotation processing.
     */
    private void closeFileObject(String typeName, FileObject fileObject) {
        /*
         * If typeName is non-null, the file object was opened as a
         * source or class file by the user.  If a file was opened as
         * a resource, typeName will be null and the file is *not*
         * subject to annotation processing.
         */
        if ((typeName != null)) {
            if (!(fileObject instanceof JavaFileObject))
                throw new AssertionError("JavaFileOject not found for " + fileObject);
            JavaFileObject javaFileObject = (JavaFileObject)fileObject;
            switch(javaFileObject.getKind()) {
            case SOURCE:
                generatedSourceNames.add(typeName);
                generatedSourceFileObjects.add(javaFileObject);
                openTypeNames.remove(typeName);
                break;

            case CLASS:
                generatedClasses.put(typeName, javaFileObject);
                openTypeNames.remove(typeName);
                break;

            default:
                break;
            }
        }
    }

}

Other Java examples (source code examples)

Here is a short list of links related to this Java JavacFiler.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.