/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.asakusafw.utils.java.jsr199.testing;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardLocation;
/**
* An implementation of {@link JavaFileManager} which stores generated contents onto the heap.
*/
public class VolatileClassOutputManager
extends ForwardingJavaFileManager<JavaFileManager> {
/**
* The Java name separator character.
*/
public static final char NAME_SEPARATOR = '.';
/**
* The path name separator character.
*/
public static final char SEGMENT_SEPARATOR = '/';
private static final char NAME_SEPARATOR_NEXT = NAME_SEPARATOR + 1;
private SortedMap<String, VolatileClassFile> classMap;
private SortedMap<String, VolatileJavaFile> sourceMap;
private SortedMap<String, VolatileResourceFile> resourceMap;
/**
* Creates a new instance.
* @param fileManager the delegation target file manager
* @throws NullPointerException if the parameter is {@code null}
*/
public VolatileClassOutputManager(JavaFileManager fileManager) {
super(fileManager);
this.classMap = new TreeMap<>();
this.sourceMap = new TreeMap<>();
this.resourceMap = new TreeMap<>();
}
/**
* Returns the generated Java source files.
* @return the generated Java source files
*/
public Collection<VolatileJavaFile> getSources() {
return new ArrayList<>(sourceMap.values());
}
/**
* Returns the generated resource files.
* @return the generated resource files
*/
public Collection<VolatileResourceFile> getResources() {
return new ArrayList<>(resourceMap.values());
}
/**
* Returns the generated Java class files.
* @return the generated Java class files
*/
public Collection<VolatileClassFile> getCompiled() {
return new ArrayList<>(classMap.values());
}
/**
* {@inheritDoc}
* {@link #getSources()}, {@link #getResources()}, and {@link #getCompiled()} will return empty collections
* after this operation. Please obtain the each result before closing this object, then the results will
* be still available after this was closed.
*/
@Override
public void close() throws IOException {
try {
super.close();
} finally {
this.sourceMap = new TreeMap<>();
this.classMap = new TreeMap<>();
this.resourceMap = new TreeMap<>();
}
}
@Override
public boolean hasLocation(Location location) {
if (location == StandardLocation.CLASS_OUTPUT) {
return true;
} else {
return super.hasLocation(location);
}
}
@Override
public FileObject getFileForInput(
Location location,
String packageName,
String relativeName) throws IOException {
if (location == StandardLocation.CLASS_OUTPUT) {
String binaryName = normalizePath(packageName, relativeName, JavaFileObject.Kind.CLASS);
if (binaryName == null) {
String path = toPath(packageName, relativeName);
return resourceMap.get(path);
}
return getJavaFileForInput(location, binaryName, JavaFileObject.Kind.CLASS);
} else if (location == StandardLocation.SOURCE_OUTPUT) {
String binaryName = normalizePath(packageName, relativeName, JavaFileObject.Kind.SOURCE);
if (binaryName == null) {
String path = toPath(packageName, relativeName);
return resourceMap.get(path);
}
return getJavaFileForInput(location, binaryName, JavaFileObject.Kind.SOURCE);
} else {
return super.getFileForInput(location, packageName, relativeName);
}
}
@Override
public FileObject getFileForOutput(
Location location,
String packageName,
String relativeName,
FileObject sibling) throws IOException {
if (location == StandardLocation.CLASS_OUTPUT) {
String binaryName = normalizePath(packageName, relativeName, JavaFileObject.Kind.CLASS);
if (binaryName == null) {
String path = toPath(packageName, relativeName);
VolatileResourceFile file = resourceMap.get(path);
if (file == null) {
file = new VolatileResourceFile(path);
resourceMap.put(path, file);
}
return file;
}
return getJavaFileForOutput(location, binaryName, JavaFileObject.Kind.CLASS, sibling);
} else if (location == StandardLocation.SOURCE_OUTPUT) {
String binaryName = normalizePath(packageName, relativeName, JavaFileObject.Kind.SOURCE);
if (binaryName == null) {
String path = toPath(packageName, relativeName);
VolatileResourceFile file = resourceMap.get(path);
if (file == null) {
file = new VolatileResourceFile(path);
resourceMap.put(path, file);
}
return file;
}
return getJavaFileForOutput(location, binaryName, JavaFileObject.Kind.SOURCE, sibling);
} else {
return super.getFileForOutput(location, packageName, relativeName, sibling);
}
}
@Override
public JavaFileObject getJavaFileForInput(
Location location,
String className,
Kind kind) throws IOException {
if (location == StandardLocation.CLASS_OUTPUT) {
String binaryName = normalizeClassName(className);
if (classMap.containsKey(binaryName)) {
return classMap.get(binaryName);
}
return null;
} else if (location == StandardLocation.SOURCE_OUTPUT) {
String binaryName = normalizeClassName(className);
if (sourceMap.containsKey(binaryName)) {
return sourceMap.get(binaryName);
}
return null;
} else {
return super.getJavaFileForInput(location, className, kind);
}
}
@Override
public JavaFileObject getJavaFileForOutput(
Location location,
String className,
Kind kind,
FileObject sibling) throws IOException {
if (location == StandardLocation.CLASS_OUTPUT) {
String binaryName = normalizeClassName(className);
if (classMap.containsKey(binaryName)) {
return classMap.get(binaryName);
}
VolatileClassFile classFile = new VolatileClassFile(binaryName);
classMap.put(binaryName, classFile);
return classFile;
} else if (location == StandardLocation.SOURCE_OUTPUT) {
String binaryName = normalizeClassName(className);
if (sourceMap.containsKey(binaryName)) {
return sourceMap.get(binaryName);
}
VolatileJavaFile javaFile = new VolatileJavaFile(className.replace('.', '/'));
sourceMap.put(binaryName, javaFile);
return javaFile;
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
@Override
public Iterable<JavaFileObject> list(
Location location,
String packageName,
Set<Kind> kinds,
boolean recurse) throws IOException {
if (location == StandardLocation.CLASS_OUTPUT) {
if (kinds.contains(JavaFileObject.Kind.CLASS) == false) {
return Collections.emptySet();
}
return inPackage(classMap, packageName, recurse);
} else if (location == StandardLocation.SOURCE_OUTPUT) {
if (kinds.contains(JavaFileObject.Kind.SOURCE) == false) {
return Collections.emptySet();
}
return inPackage(sourceMap, packageName, recurse);
} else {
return super.list(location, packageName, kinds, recurse);
}
}
@Override
public boolean isSameFile(FileObject a, FileObject b) {
if (a instanceof VolatileJavaFile || a instanceof VolatileClassFile) {
return a.toUri().equals(b.toUri());
}
if (b instanceof VolatileJavaFile || b instanceof VolatileClassFile) {
return b.toUri().equals(a.toUri());
}
return super.isSameFile(a, b);
}
private Collection<JavaFileObject> inPackage(
SortedMap<String, ? extends JavaFileObject> all,
String packageName,
boolean recurse) {
assert all != null;
assert packageName != null;
Map<String, ? extends JavaFileObject> map;
int prefix;
if (packageName.isEmpty()) {
map = all;
prefix = 0;
} else {
String binaryName = normalizeClassName(packageName);
map = all.subMap(binaryName + NAME_SEPARATOR, binaryName + NAME_SEPARATOR_NEXT);
prefix = binaryName.length() + 1;
}
if (recurse) {
return new ArrayList<>(map.values());
}
List<JavaFileObject> results = new ArrayList<>();
for (Map.Entry<String, ? extends JavaFileObject> entry : map.entrySet()) {
String className = entry.getKey();
if (className.indexOf(NAME_SEPARATOR, prefix) < 0) {
results.add(entry.getValue());
}
}
return results;
}
private String normalizePath(String packageName, String relativeName, JavaFileObject.Kind kind) {
assert packageName != null;
assert relativeName != null;
if (relativeName.endsWith(kind.extension) == false) {
return null;
}
String strippedRelativeName = relativeName.substring(0, relativeName.length() - kind.extension.length());
if (packageName.isEmpty()) {
return normalizeClassName(strippedRelativeName);
}
String className = packageName + SEGMENT_SEPARATOR + strippedRelativeName;
return normalizeClassName(className);
}
private static String normalizeClassName(String className) {
assert className != null;
return className.replace(SEGMENT_SEPARATOR, NAME_SEPARATOR);
}
private String toPath(String packageName, String relativeName) {
assert packageName != null;
assert relativeName != null;
if (packageName.isEmpty()) {
return relativeName;
}
return packageName + SEGMENT_SEPARATOR + relativeName;
}
}