/*
* 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.google.devtools.j2objc.util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.devtools.j2objc.ast.CompilationUnit;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
/**
* Manages the mapping of types to their header files.
*/
public class HeaderMap {
/**
* Public packages included by the j2objc libraries. This list is necessary so
* that when package directories are suppressed, the platform headers can still
* be found.
*/
// TODO(tball): move this list to a distributed file, perhaps generated by build.
private static final Set<String> PLATFORM_PACKAGES = Sets.newHashSet(new String[] {
"android",
"com.android.internal.util",
"com.google.common",
"com.google.common.annotations",
"com.google.common.base",
"com.google.common.cache",
"com.google.common.collect",
"com.google.common.hash",
"com.google.common.io",
"com.google.common.math",
"com.google.common.net",
"com.google.common.primitives",
"com.google.common.util",
"com.google.j2objc",
"com.google.protobuf",
"dalvik",
"java",
"javax",
"junit",
"libcore",
"org.apache.harmony",
"org.hamcrest",
"org.json",
"org.junit",
"org.kxml2",
"org.mockito",
"org.w3c",
"org.xml.sax",
"org.xmlpull",
"sun.misc",
});
private static final String DEFAULT_HEADER_MAPPING_FILE = "mappings.j2objc";
/**
* Types of output file generation. Output files are generated in
* the specified output directory in an optional sub-directory.
*/
public static enum OutputStyleOption {
/** Use the class's package, like javac.*/
PACKAGE,
/** Use the relative directory of the input file. */
SOURCE,
/** Don't use a relative directory. */
NONE
}
private OutputStyleOption outputStyle = OutputStyleOption.PACKAGE;
// Variant of SOURCE style. Sources from .jar files are combined into a single output header and
// source file.
private boolean combineJars = false;
// Variant of SOURCE style. Annotation generated sources are included in the same output as the
// source they are generated from.
private boolean includeGeneratedSources = false;
private List<String> inputMappingFiles = null;
private File outputMappingFile = null;
private final Map<String, String> map = Maps.newHashMap();
public void setOutputStyle(OutputStyleOption outputStyle) {
this.outputStyle = outputStyle;
}
public void setCombineJars() {
outputStyle = OutputStyleOption.SOURCE;
combineJars = true;
}
public void setIncludeGeneratedSources() {
outputStyle = OutputStyleOption.SOURCE;
includeGeneratedSources = true;
}
public void setMappingFiles(String fileList) {
if (fileList.isEmpty()) {
// For when user supplies an empty mapping files list. Otherwise the default will be used.
inputMappingFiles = Collections.emptyList();
} else {
inputMappingFiles = ImmutableList.copyOf(fileList.split(","));
}
}
public void setOutputMappingFile(File outputMappingFile) {
this.outputMappingFile = outputMappingFile;
}
/**
* If true, generated source locations are determined as a function of the input source location
* and not the package of the input source.
*/
public boolean useSourceDirectories() {
return outputStyle == OutputStyleOption.SOURCE;
}
public boolean combineSourceJars() {
return outputStyle == OutputStyleOption.SOURCE && combineJars;
}
public boolean includeGeneratedSources() {
return outputStyle == OutputStyleOption.SOURCE && includeGeneratedSources;
}
public String get(TypeElement type) {
String explicitHeader = ElementUtil.getHeader(type);
if (explicitHeader != null) {
return explicitHeader;
}
String qualifiedName = ElementUtil.getQualifiedName(type);
String mappedHeader = map.get(qualifiedName);
if (mappedHeader != null) {
return mappedHeader;
}
String name = ElementUtil.getName(type);
PackageElement pkg = ElementUtil.getPackage(type);
return outputDirFromPackage(pkg) + name + ".h";
}
@VisibleForTesting
public String getMapped(String qualifiedName) {
return map.get(qualifiedName);
}
public String getOutputPath(CompilationUnit unit) {
return outputDirFromPackage(unit.getPackage().getPackageElement()) + unit.getMainTypeName();
}
private String outputDirFromPackage(PackageElement pkg) {
if (pkg == null || pkg.isUnnamed()) {
return "";
}
String pkgName = ElementUtil.getName(pkg);
OutputStyleOption style = outputStyle;
if (isPlatformPackage(pkgName)) {
// Use package directories for platform classes if they do not have an entry in the header
// mapping.
style = OutputStyleOption.PACKAGE;
}
switch (style) {
case PACKAGE:
return ElementUtil.getName(pkg).replace('.', File.separatorChar) + File.separatorChar;
default:
return "";
}
}
public void put(String qualifiedName, String header) {
map.put(qualifiedName, header);
}
private static boolean isPlatformPackage(String pkgName) {
String[] parts = pkgName.split("\\.");
String pkg = null;
for (int i = 0; i < parts.length; i++) {
pkg = i == 0 ? parts[0] : UnicodeUtils.format("%s.%s", pkg, parts[i]);
if (PLATFORM_PACKAGES.contains(pkg)) {
return true;
}
}
return false;
}
public void loadMappings() {
try {
if (inputMappingFiles == null) {
try {
loadMappingsFromProperties(FileUtil.loadProperties(DEFAULT_HEADER_MAPPING_FILE));
} catch (FileNotFoundException e) {
// Don't fail if mappings aren't configured and the default mapping is absent.
}
} else {
for (String resourceName : inputMappingFiles) {
loadMappingsFromProperties(FileUtil.loadProperties(resourceName));
}
}
} catch (IOException e) {
ErrorUtil.error(e.getMessage());
}
}
private void loadMappingsFromProperties(Properties mappings) {
Enumeration<?> keyIterator = mappings.propertyNames();
while (keyIterator.hasMoreElements()) {
String key = (String) keyIterator.nextElement();
map.put(key, mappings.getProperty(key));
}
}
public void printMappings() {
if (outputMappingFile == null) {
return;
}
try {
if (!outputMappingFile.exists()) {
outputMappingFile.getParentFile().mkdirs();
outputMappingFile.createNewFile();
}
PrintWriter writer = new PrintWriter(outputMappingFile, "UTF-8");
for (Map.Entry<String, String> entry : map.entrySet()) {
writer.println(UnicodeUtils.format("%s=%s", entry.getKey(), entry.getValue()));
}
writer.close();
} catch (IOException e) {
ErrorUtil.error(e.getMessage());
}
}
}