/*
* 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.util;
import com.sun.tools.mjavac.util.BaseFileObject;
import com.sun.tools.mjavac.util.Context;
import com.sun.tools.mjavac.util.JavacFileManager;
import com.sun.tools.mjavac.util.List;
import com.sun.tools.mjavac.util.ListBuffer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
public class VisageFileManager extends JavacFileManager {
/**
* The Visage source file extension.
* @see javax.tools.JavaFileObject.Kind.SOURCE
*/
public static final String VISAGE_SOURCE_SUFFIX = ".visage";
/**
* Register a Context.Factory to create a VisageFileManager.
*/
public static void preRegister(final Context context) {
context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
public JavaFileManager make() {
return new VisageFileManager(context, true, null);
}
});
}
public VisageFileManager(Context context, boolean register, Charset charset) {
super(context, register, charset);
}
@Override
protected JavaFileObject.Kind getKind(String extension) {
if (extension.equals(JavaFileObject.Kind.CLASS.extension))
return JavaFileObject.Kind.CLASS;
else if (extension.equals(VISAGE_SOURCE_SUFFIX))
return JavaFileObject.Kind.SOURCE;
else if (extension.equals(JavaFileObject.Kind.HTML.extension))
return JavaFileObject.Kind.HTML;
else
return JavaFileObject.Kind.OTHER;
}
@Override
public JavaFileObject getRegularFile(File file) {
return new DelegateJavaFileObject(super.getRegularFile(file));
}
@Override
public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
Iterable<? extends File> files)
{
Iterable<? extends JavaFileObject> objs = super.getJavaFileObjectsFromFiles(files);
ArrayList<DelegateJavaFileObject> result = new ArrayList<DelegateJavaFileObject>();
for (JavaFileObject jfo : objs)
result.add(new DelegateJavaFileObject(jfo));
return result;
}
@Override
public JavaFileObject getJavaFileForInput(Location location,
String className,
JavaFileObject.Kind kind)
throws IOException
{
nullCheck(location);
// validateClassName(className);
nullCheck(className);
nullCheck(kind);
if (!sourceOrClass.contains(kind))
throw new IllegalArgumentException("Invalid kind " + kind);
String name = externalizeFileName(className, kind);
Iterable<? extends File> path = getLocation(location);
if (path == null)
return null;
for (File dir: path) {
if (dir.isDirectory()) {
File f = new File(dir, name.replace('/', File.separatorChar));
if (f.exists())
return new DelegateJavaFileObject(getRegularFile(f));
} else {
Archive a = openArchive(dir);
if (a.contains(name)) {
int i = name.lastIndexOf('/');
String dirname = name.substring(0, i+1);
String basename = name.substring(i+1);
return new DelegateJavaFileObject(a.getFileObject(dirname, basename));
}
}
}
return null;
}
@Override
public Iterable<JavaFileObject> list(Location location,
String packageName,
Set<JavaFileObject.Kind> kinds,
boolean recurse)
throws IOException
{
if (!kinds.contains(JavaFileObject.Kind.SOURCE))
return super.list(location, packageName, kinds, recurse);
nullCheck(packageName);
nullCheck(kinds);
Iterable<? extends File> path = getLocation(location);
if (path == null)
return List.nil();
String subdirectory = packageName.replace('.', File.separatorChar);
ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();
for (File directory : path)
listDirectory(directory, subdirectory, kinds, recurse, results);
return results.toList();
}
private static <T> T nullCheck(T o) {
o.getClass(); // null check
return o;
}
private static String externalizeFileName(CharSequence name, JavaFileObject.Kind kind) {
String basename = name.toString().replace('.', File.separatorChar);
String suffix = kind == JavaFileObject.Kind.SOURCE ?
VISAGE_SOURCE_SUFFIX : kind.extension;
return basename + suffix;
}
@Override
public JavaFileObject getJavaFileForOutput(Location location,
String className,
JavaFileObject.Kind kind,
FileObject sibling)
throws IOException {
if (sibling != null && sibling instanceof DelegateJavaFileObject) {
sibling = ((DelegateJavaFileObject)sibling).delegate;
}
return super.getJavaFileForOutput(location, className, kind, sibling);
}
/**
* Insert all files in subdirectory `subdirectory' of `directory' which end
* in one of the extensions in `extensions' into packageSym.
*/
private void listDirectory(File directory,
String subdirectory,
Set<JavaFileObject.Kind> fileKinds,
boolean recurse,
ListBuffer<JavaFileObject> l) {
boolean isFile = directory.isFile();
if (isFile) {
Archive archive = null;
try {
archive = openArchive(directory);
} catch (IOException ex) {
log.error("error.reading.file",
directory, ex.getLocalizedMessage());
return;
}
if (subdirectory.length() != 0) {
subdirectory = subdirectory.replace('\\', '/');
if (!subdirectory.endsWith("/"))
subdirectory = subdirectory + "/";
}
else {
if (File.separatorChar == '/') {
subdirectory = subdirectory.replace('\\', '/');
}
else {
subdirectory = subdirectory.replace('/', '\\');
}
if (!subdirectory.endsWith(File.separator))
subdirectory = subdirectory + File.separator;
}
List<String> files = archive.getFiles(subdirectory);
if (files != null) {
for (String file; !files.isEmpty(); files = files.tail) {
file = files.head;
if (isValidFile(file, fileKinds)) {
l.append(archive.getFileObject(subdirectory, file));
}
}
}
if (recurse) {
for (String s: archive.getSubdirectories()) {
if (s.startsWith(subdirectory) && !s.equals(subdirectory)) {
// Because the archive map is a flat list of directories,
// the enclosing loop will pick up all child subdirectories.
// Therefore, there is no need to recurse deeper.
listDirectory(directory, s, fileKinds, false, l);
}
}
}
} else {
File d = subdirectory.length() != 0
? new File(directory, subdirectory)
: directory;
if (!caseMapCheck(d, subdirectory))
return;
File[] files = d.listFiles();
if (files == null)
return;
for (File f: files) {
String fname = f.getName();
if (f.isDirectory()) {
if (recurse && SourceVersion.isIdentifier(fname)) {
listDirectory(directory,
subdirectory + File.separator + fname,
fileKinds,
recurse,
l);
}
} else {
if (isValidFile(fname, fileKinds)) {
JavaFileObject fe =
new DelegateJavaFileObject(super.getRegularFile(new File(d, fname)));
l.append(fe);
}
}
}
}
}
private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
int lastDot = s.lastIndexOf(".");
String extn = (lastDot == -1 ? s : s.substring(lastDot));
JavaFileObject.Kind kind = getKind(extn);
return fileKinds.contains(kind);
}
@Override
public String inferBinaryName(Location location, JavaFileObject file) {
file.getClass(); // null check
location.getClass(); // null check
// Need to match the path semantics of list(location, ...)
Iterable<? extends File> path = getLocation(location);
if (path == null) {
return null;
}
if (file instanceof DelegateJavaFileObject) {
DelegateJavaFileObject r = (DelegateJavaFileObject) file;
String rPath = r.getPath();
for (File dir: path) {
String dPath = dir.getPath();
if (!dPath.endsWith(File.separator))
dPath += File.separator;
if (rPath.regionMatches(true, 0, dPath, 0, dPath.length())
&& new File(rPath.substring(0, dPath.length())).equals(new File(dPath))) {
String relativeName = rPath.substring(dPath.length());
return removeExtension(relativeName).replace(File.separatorChar, '.');
}
}
}
return super.inferBinaryName(location, file);
}
private static String removeExtension(String fileName) {
int lastDot = fileName.lastIndexOf(".");
return (lastDot == -1 ? fileName : fileName.substring(0, lastDot));
}
private static final boolean fileSystemIsCaseSensitive =
File.separatorChar == '/';
/** Hack to make Windows case sensitive. Test whether given path
* ends in a string of characters with the same case as given name.
* Ignore file separators in both path and name.
*/
private boolean caseMapCheck(File f, String name) {
if (fileSystemIsCaseSensitive) return true;
// Note that getCanonicalPath() returns the case-sensitive
// spelled file name.
String path;
try {
path = f.getCanonicalPath();
} catch (IOException ex) {
return false;
}
char[] pcs = path.toCharArray();
char[] ncs = name.toCharArray();
int i = pcs.length - 1;
int j = ncs.length - 1;
while (i >= 0 && j >= 0) {
while (i >= 0 && pcs[i] == File.separatorChar) i--;
while (j >= 0 && ncs[j] == File.separatorChar) j--;
if (i >= 0 && j >= 0) {
if (pcs[i] != ncs[j]) return false;
i--;
j--;
}
}
return j < 0;
}
private final Set<JavaFileObject.Kind> sourceOrClass =
EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
private static class DelegateJavaFileObject extends BaseFileObject {
JavaFileObject delegate;
boolean isVisageSourceFile;
DelegateJavaFileObject(JavaFileObject jfo) {
delegate = jfo;
isVisageSourceFile = jfo.getName().endsWith(VISAGE_SOURCE_SUFFIX);
}
@Override
public Kind getKind() {
return isVisageSourceFile ? JavaFileObject.Kind.SOURCE : delegate.getKind();
}
//@Override
public boolean isNameCompatible(String cn, Kind kind) {
cn.getClass(); // null check
if (kind == Kind.OTHER && getKind() != kind)
return false;
String suffix = (kind == JavaFileObject.Kind.SOURCE) ? ".visage" : kind.extension;
String n = cn + suffix;
if (delegate.getName().equals(n))
return true;
if (getName().equalsIgnoreCase(n)) {
try {
// allow for Windows
File f = new File(getPath());
return (f.getCanonicalFile().getName().equals(n));
} catch (IOException e) {
}
}
return false;
}
@Override
public NestingKind getNestingKind() {
return delegate.getNestingKind();
}
@Override
public Modifier getAccessLevel() {
return delegate.getAccessLevel();
}
public URI toUri() {
return delegate.toUri();
}
public String getName() {
return delegate.getName();
}
/** @deprecated see bug 6410637 */
@Deprecated @Override
public String getPath() {
return delegate instanceof BaseFileObject ?
((BaseFileObject)delegate).getPath() : getName();
}
public InputStream openInputStream() throws IOException {
return delegate.openInputStream();
}
public OutputStream openOutputStream() throws IOException {
return delegate.openOutputStream();
}
@Override
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
return delegate.openReader(ignoreEncodingErrors);
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return delegate.getCharContent(ignoreEncodingErrors);
}
public Writer openWriter() throws IOException {
return delegate.openWriter();
}
public long getLastModified() {
return delegate.getLastModified();
}
public boolean delete() {
return delegate.delete();
}
@Override
public String toString() {
return delegate.toString();
}
}
}