/*
* Copyright 2008-2009 Sun Microsystems, Inc. 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.
*
* 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package org.visage.tools.script;
import org.visage.tools.util.VisageFileManager;
import java.io.*;
import java.nio.CharBuffer;
import java.util.*;
import java.util.Set;
import javax.lang.model.element.NestingKind;
import javax.tools.*;
import javax.tools.JavaFileObject.Kind;
import java.net.URL;
import java.net.URI;
import java.nio.charset.CharsetDecoder;
import javax.lang.model.element.Modifier;
/**
* JavaFileManager that keeps compiled .class bytes in memory.
*
* @author A. Sundararajan
*/
public final class MemoryFileManager extends ForwardingJavaFileManager {
private ClassLoader parentClassLoader;
// A map in which the key is package name and the value is list of
// classes in that package.
Map<String, List<String>> packageMap;
Map<String,ClassOutputBuffer> emittedClasses;
/** Visage source file extension. */
private final static String EXT = ".visage";
public MemoryFileManager(JavaFileManager fileManager, ClassLoader cl,
Map<String, List<String>> pkgMap,
Map<String, ClassOutputBuffer> clbuffers) {
super(fileManager);
parentClassLoader = cl;
packageMap = pkgMap;
this.emittedClasses = clbuffers;
}
@Override
public void close() throws IOException {
}
@Override
public void flush() throws IOException {
}
/**
* A file object used to represent a Java class coming from the parent class loader.
* Handles the case of .class file as well as the .class jar entry. We can not
* extend SimpleJavaFileObject here because that class makes certain assumptions
* about the URI (works only for file system .class files and not for jar:file:.. URIs).
*/
private static class ClassResource implements JavaFileObject {
private URL url;
private String binaryName;
static URI toURI(URL u) {
try {
return u.toURI();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
ClassResource(URL url, String binaryName) {
this.url = url;
this.binaryName = binaryName;
}
//@Override
public Kind getKind() {
return Kind.CLASS;
}
public String getBinaryName() {
return binaryName;
}
//@Override
public String getName() {
return getBinaryName();
}
//@Override
public boolean isNameCompatible(String simpleName,
JavaFileObject.Kind kind) {
return (kind == Kind.CLASS) &&
url.toString().endsWith("/" + simpleName + ".class");
}
//@Override
public boolean delete() {
return false;
}
//@Override
public long getLastModified() {
return 0L;
}
//@Override
public Writer openWriter() throws IOException {
throw new UnsupportedOperationException("openWriter");
}
//@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
throw new UnsupportedOperationException("getCharContent");
}
//@Override
public OutputStream openOutputStream() throws IOException {
throw new UnsupportedOperationException("openOutputStream");
}
//@Override
public InputStream openInputStream() throws IOException {
return url.openStream();
}
//@Override
public URI toUri() {
return toURI(url);
}
//@Override
public NestingKind getNestingKind() {
return null;
}
//@Override
public Modifier getAccessLevel() {
return null;
}
//@Override
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
return new InputStreamReader(openInputStream(), getDecoder(ignoreEncodingErrors));
}
protected CharsetDecoder getDecoder(boolean ignoreEncodingErrors) {
throw new UnsupportedOperationException("getDecoder");
}
}
/**
* A file object used to represent Java source coming from a string.
*/
private static class StringInputBuffer extends SimpleJavaFileObject {
final String code;
final boolean isVisageSourceFile;
String binaryName;
public String getBinaryName() {
return binaryName.equals("__VISAGE_SCRIPT__.visage") ? "__VISAGE_SCRIPT__" : binaryName;
}
StringInputBuffer(String name, String code) {
super(toURI(name), Kind.SOURCE);
this.code = code;
binaryName = name;
isVisageSourceFile = name.endsWith(VisageFileManager.VISAGE_SOURCE_SUFFIX);
}
@Override
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
public Reader openReader() {
return new StringReader(code);
}
@Override
public Kind getKind() {
//return isVisageSourceFile ? JavaFileObject.Kind.SOURCE : super.getKind();
return JavaFileObject.Kind.SOURCE;
}
@Override
public String getName() {
return super.getName();
}
@Override
public NestingKind getNestingKind() {
return super.getNestingKind();
}
@Override
public boolean isNameCompatible(String simpleName, Kind kind) {
return super.isNameCompatible(simpleName, kind);
}
@Override
public InputStream openInputStream() throws IOException {
return super.openInputStream();
}
@Override
public OutputStream openOutputStream() throws IOException {
return super.openOutputStream();
}
@Override
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
return super.openReader(ignoreEncodingErrors);
}
@Override
public Writer openWriter() throws IOException {
return super.openWriter();
}
}
/**
* A file object that stores Java bytecode into the emittedClasses map.
*/
public class ClassOutputBuffer extends SimpleJavaFileObject {
private String name;
ClassOutputBuffer(String name) {
super(toURI(name), Kind.CLASS);
this.name = name;
}
public String getBinaryName() {
return name;
}
byte[] bytes;
@Override
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
@Override
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream)out;
bytes = bos.toByteArray();
}
};
}
@Override
public InputStream openInputStream() throws IOException {
if (bytes == null)
throw new UnsupportedOperationException("openInputStream");
return new ByteArrayInputStream(bytes);
}
}
@Override
public JavaFileObject getJavaFileForInput(JavaFileManager.Location location,
String className,
Kind kind) throws IOException {
if (kind == Kind.CLASS) {
URL res =
parentClassLoader.getResource(className.replace('.', '/') + ".class");
if (res != null) {
return new ClassResource(res, className);
}
}
return super.getJavaFileForInput(location, className, kind);
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
String className,
Kind kind,
FileObject sibling) throws IOException {
if (kind == Kind.CLASS) {
ClassOutputBuffer buf = new ClassOutputBuffer(className);
emittedClasses.put(className, buf);
return buf;
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
@Override
public Iterable list(JavaFileManager.Location location,
String packageName,
Set kinds,
boolean recurse)
throws IOException
{
List results = new LinkedList();
if (kinds.contains(Kind.CLASS)) {
// From the list of .class entries of the given package,
// construct JavaFileObjects for that package.
if (packageMap.containsKey(packageName)) {
for (String cl : packageMap.get(packageName)) {
String dir = packageName.replace('.', '/');
URL res = parentClassLoader.getResource(dir + "/" + cl + ".class");
// add a JavaFileObject only if the class loader can find
// resource URL for the given .class.
if (res != null) {
String binaryName = packageName + "." + cl;
results.add(new ClassResource(res, binaryName));
}
}
}
}
Iterable result = super.list(location, packageName, kinds, recurse);
for (Object o : result) {
results.add(o);
}
String prefix = packageName.equals("") ? "" : packageName + ".";
for (ClassOutputBuffer b : emittedClasses.values()) {
String name = b.getName().replace("/", ".");
name = name.substring(1, name.length() - (name.endsWith(EXT) ? EXT.length() : 0));
if (prefix.length() == 0) {
if (!name.contains(".")) {
results.add(b);
}
} else {
if (name.startsWith(prefix)) {
name = name.substring(prefix.length());
if (!name.contains(".")) {
results.add(b);
}
}
}
}
return results;
}
JavaFileObject makeStringSource(String name, String code) {
return new StringInputBuffer(name, code);
}
@Override
public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof StringInputBuffer) {
return ((StringInputBuffer)file).getBinaryName();
} else if (file instanceof ClassOutputBuffer) {
return ((ClassOutputBuffer)file).getBinaryName();
} else if (file instanceof ClassResource) {
return ((ClassResource)file).getBinaryName();
}
return super.inferBinaryName(location, file);
}
static URI toURI(String name) {
File file = new File(name);
if (file.exists()) {
return file.toURI();
} else {
try {
final StringBuilder newUri = new StringBuilder();
newUri.append("mfm:///");
newUri.append(name.replace('.', '/'));
if(name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
return URI.create(newUri.toString());
} catch (Exception exp) {
return URI.create("mfm:///org/visage/tools/script/visage_source");
}
}
}
}