Add j2objc dependency
This commit is contained in:
parent
97b1fc405a
commit
6bf6fcfebc
5 changed files with 819 additions and 0 deletions
1
build-scripts/.gitignore
vendored
1
build-scripts/.gitignore
vendored
|
@ -9,3 +9,4 @@ local.properties
|
||||||
*.stillfailing
|
*.stillfailing
|
||||||
*.failed
|
*.failed
|
||||||
*.log
|
*.log
|
||||||
|
j2objc
|
||||||
|
|
8
build-scripts/configure_translator
Executable file
8
build-scripts/configure_translator
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
git clone https://code.google.com/p/j2objc/
|
||||||
|
cd j2objc
|
||||||
|
git apply ../j2objc.patch
|
||||||
|
mvn eclipse:eclipse
|
||||||
|
mvn -DskipTests=true install
|
||||||
|
cd ../net.osmand.translator
|
||||||
|
mvn eclipse:eclipse
|
||||||
|
mvn install
|
42
build-scripts/j2objc.patch
Normal file
42
build-scripts/j2objc.patch
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
diff --git a/pom.xml b/pom.xml
|
||||||
|
index 39853de..c04fffb 100644
|
||||||
|
--- a/pom.xml
|
||||||
|
+++ b/pom.xml
|
||||||
|
@@ -21,7 +21,7 @@
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.google.j2objc</groupId>
|
||||||
|
<artifactId>j2objc</artifactId>
|
||||||
|
- <packaging>pom</packaging>
|
||||||
|
+ <packaging>jar</packaging>
|
||||||
|
<version>0.8</version>
|
||||||
|
<name>j2objc</name>
|
||||||
|
<url>https://code.google.com/p/j2objc/</url>
|
||||||
|
@@ -29,6 +29,15 @@
|
||||||
|
<build>
|
||||||
|
<directory>build_result</directory>
|
||||||
|
<plugins>
|
||||||
|
+ <plugin>
|
||||||
|
+ <groupId>org.apache.maven.plugins</groupId>
|
||||||
|
+ <artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
+ <version>2.0.2</version>
|
||||||
|
+ <configuration>
|
||||||
|
+ <source>1.5</source>
|
||||||
|
+ <target>1.5</target>
|
||||||
|
+ </configuration>
|
||||||
|
+ </plugin>
|
||||||
|
<!-- Copy dependent jars -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
diff --git a/src/main/java/com/google/devtools/j2objc/Plugin.java b/src/main/java/com/google/devtools/j2objc/Plugin.java
|
||||||
|
index 4f1f883..40b0985 100644
|
||||||
|
--- a/src/main/java/com/google/devtools/j2objc/Plugin.java
|
||||||
|
+++ b/src/main/java/com/google/devtools/j2objc/Plugin.java
|
||||||
|
@@ -40,7 +40,7 @@ public abstract class Plugin {
|
||||||
|
* options and interpret them differently. Called before any processing is
|
||||||
|
* started.
|
||||||
|
*/
|
||||||
|
- void initPlugin(String pluginOptions) throws IOException {
|
||||||
|
+ public void initPlugin(String pluginOptions) throws IOException {
|
||||||
|
String[] optionComponents = pluginOptions.split(",");
|
||||||
|
|
||||||
|
for (String option : optionComponents) {
|
|
@ -49,6 +49,12 @@
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.j2objc</groupId>
|
||||||
|
<artifactId>j2objc</artifactId>
|
||||||
|
<version>0.8</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
|
|
|
@ -0,0 +1,762 @@
|
||||||
|
package net.osmand.translator;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarInputStream;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipException;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.core.compiler.IProblem;
|
||||||
|
import org.eclipse.jdt.core.dom.AST;
|
||||||
|
import org.eclipse.jdt.core.dom.ASTNode;
|
||||||
|
import org.eclipse.jdt.core.dom.ASTParser;
|
||||||
|
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||||
|
import org.eclipse.jface.text.BadLocationException;
|
||||||
|
import org.eclipse.jface.text.Document;
|
||||||
|
import org.eclipse.text.edits.MalformedTreeException;
|
||||||
|
import org.eclipse.text.edits.TextEdit;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.io.CharStreams;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.google.devtools.j2objc.Options;
|
||||||
|
import com.google.devtools.j2objc.Plugin;
|
||||||
|
import com.google.devtools.j2objc.gen.ObjectiveCHeaderGenerator;
|
||||||
|
import com.google.devtools.j2objc.gen.ObjectiveCImplementationGenerator;
|
||||||
|
import com.google.devtools.j2objc.sym.Symbols;
|
||||||
|
import com.google.devtools.j2objc.translate.AnonymousClassConverter;
|
||||||
|
import com.google.devtools.j2objc.translate.Autoboxer;
|
||||||
|
import com.google.devtools.j2objc.translate.DeadCodeEliminator;
|
||||||
|
import com.google.devtools.j2objc.translate.DestructorGenerator;
|
||||||
|
import com.google.devtools.j2objc.translate.GwtConverter;
|
||||||
|
import com.google.devtools.j2objc.translate.InitializationNormalizer;
|
||||||
|
import com.google.devtools.j2objc.translate.InnerClassExtractor;
|
||||||
|
import com.google.devtools.j2objc.translate.JavaToIOSMethodTranslator;
|
||||||
|
import com.google.devtools.j2objc.translate.JavaToIOSTypeConverter;
|
||||||
|
import com.google.devtools.j2objc.translate.Rewriter;
|
||||||
|
import com.google.devtools.j2objc.types.Types;
|
||||||
|
import com.google.devtools.j2objc.util.ASTNodeException;
|
||||||
|
import com.google.devtools.j2objc.util.DeadCodeMap;
|
||||||
|
import com.google.devtools.j2objc.util.NameTable;
|
||||||
|
import com.google.devtools.j2objc.util.ProGuardUsageParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translation tool for generating cpp source files from Java sources.
|
||||||
|
*/
|
||||||
|
public class J2ObjC {
|
||||||
|
private static String currentFileName;
|
||||||
|
private static CompilationUnit currentUnit;
|
||||||
|
private static int nFiles = 0;
|
||||||
|
private static int nErrors = 0;
|
||||||
|
private static int nWarnings = 0;
|
||||||
|
|
||||||
|
public enum Language {
|
||||||
|
OBJECTIVE_C(".h"), OBJECTIVE_CPP(".cpp");
|
||||||
|
|
||||||
|
private final String suffix;
|
||||||
|
|
||||||
|
private Language(String suffix) {
|
||||||
|
this.suffix = suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSuffix() {
|
||||||
|
return suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Always enable assertions in translator.
|
||||||
|
ClassLoader loader = J2ObjC.class.getClassLoader();
|
||||||
|
if (loader != null) {
|
||||||
|
loader.setPackageAssertionStatus(J2ObjC.class.getPackage().getName(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(J2ObjC.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a specified Java source file and generate Objective C header(s)
|
||||||
|
* and implementation file(s) from it.
|
||||||
|
*
|
||||||
|
* @param filename the source file to translate
|
||||||
|
*/
|
||||||
|
void translate(String filename) throws IOException {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
int beginningErrorLevel = getCurrentErrorLevel();
|
||||||
|
logger.finest("reading " + filename);
|
||||||
|
|
||||||
|
// Read file
|
||||||
|
currentFileName = filename;
|
||||||
|
String source = getSource(filename);
|
||||||
|
if (source == null) {
|
||||||
|
error("no such file: " + filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long readTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// Parse and resolve source
|
||||||
|
currentUnit = parse(filename, source);
|
||||||
|
long compileTime = System.currentTimeMillis();
|
||||||
|
if (getCurrentErrorLevel() > beginningErrorLevel) {
|
||||||
|
return; // Continue to next file.
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.finest("translating " + filename);
|
||||||
|
long translateTime = 0L;
|
||||||
|
initializeTranslation(currentUnit);
|
||||||
|
try {
|
||||||
|
String newSource = translate(currentUnit, source);
|
||||||
|
translateTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (currentUnit.types().isEmpty()) {
|
||||||
|
logger.finest("skipping dead file " + filename);
|
||||||
|
} else {
|
||||||
|
if (Options.printConvertedSources()) {
|
||||||
|
saveConvertedSource(filename, newSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.finest(
|
||||||
|
"writing output file(s) to " + Options.getOutputDirectory().getAbsolutePath());
|
||||||
|
|
||||||
|
// write header
|
||||||
|
ObjectiveCHeaderGenerator.generate(filename, source, currentUnit);
|
||||||
|
|
||||||
|
// write implementation file
|
||||||
|
ObjectiveCImplementationGenerator.generate(
|
||||||
|
filename, Options.getLanguage(), currentUnit, source);
|
||||||
|
}
|
||||||
|
} catch (ASTNodeException e) {
|
||||||
|
error(e);
|
||||||
|
} finally {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
printTimingInfo(readTime - startTime, compileTime - readTime, translateTime - compileTime,
|
||||||
|
endTime - translateTime, endTime - startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompilationUnit parse(String filename, String source) {
|
||||||
|
logger.finest("parsing " + filename);
|
||||||
|
ASTParser parser = ASTParser.newParser(AST.JLS3);
|
||||||
|
Map<String, String> compilerOptions = Options.getCompilerOptions();
|
||||||
|
parser.setCompilerOptions(compilerOptions);
|
||||||
|
parser.setSource(source.toCharArray());
|
||||||
|
parser.setResolveBindings(true);
|
||||||
|
setPaths(parser);
|
||||||
|
parser.setUnitName(filename);
|
||||||
|
CompilationUnit unit = (CompilationUnit) parser.createAST(null);
|
||||||
|
|
||||||
|
for (IProblem problem : getCompilationErrors(unit)) {
|
||||||
|
if (problem.isError()) {
|
||||||
|
error(String.format("%s:%s: %s",
|
||||||
|
filename, problem.getSourceLineNumber(), problem.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<IProblem> getCompilationErrors(CompilationUnit unit) {
|
||||||
|
List<IProblem> errors = Lists.newArrayList();
|
||||||
|
for (IProblem problem : unit.getProblems()) {
|
||||||
|
if (problem.isError()) {
|
||||||
|
if (((problem.getID() & IProblem.ImportRelated) > 0) && Options.ignoreMissingImports()) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
errors.add(problem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanup() {
|
||||||
|
NameTable.cleanup();
|
||||||
|
Symbols.cleanup();
|
||||||
|
Types.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes dead types and methods, declared in a dead code map.
|
||||||
|
*
|
||||||
|
* @param unit the compilation unit created by ASTParser
|
||||||
|
* @param source the Java source used by ASTParser
|
||||||
|
* @return the rewritten source
|
||||||
|
* @throws AssertionError if the dead code eliminator makes invalid edits
|
||||||
|
*/
|
||||||
|
public static String removeDeadCode(CompilationUnit unit, String source) {
|
||||||
|
if (Options.getDeadCodeMap() == null) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
logger.finest("removing dead code");
|
||||||
|
new DeadCodeEliminator(Options.getDeadCodeMap()).run(unit);
|
||||||
|
|
||||||
|
Document doc = new Document(source);
|
||||||
|
TextEdit edit = unit.rewrite(doc, Options.getCompilerOptions());
|
||||||
|
try {
|
||||||
|
edit.apply(doc);
|
||||||
|
} catch (MalformedTreeException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (BadLocationException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
return doc.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] removeDeadCode(String[] files) throws IOException {
|
||||||
|
loadDeadCodeMap();
|
||||||
|
if (Options.getDeadCodeMap() != null) {
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
String filename = files[i];
|
||||||
|
logger.finest("reading " + filename);
|
||||||
|
|
||||||
|
if (filename.endsWith(".java")) {
|
||||||
|
// Read file
|
||||||
|
String source = getSource(filename);
|
||||||
|
if (source == null) {
|
||||||
|
error("no such file: " + filename);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
String newPath = removeDeadCode(filename, source);
|
||||||
|
if (!filename.equals(newPath)) {
|
||||||
|
files[i] = newPath;
|
||||||
|
}
|
||||||
|
} else if (filename.endsWith(".jar")) {
|
||||||
|
File f = new File(filename);
|
||||||
|
if (f.exists() && f.isFile()) {
|
||||||
|
ZipFile zfile = new ZipFile(f);
|
||||||
|
try {
|
||||||
|
Enumeration<? extends ZipEntry> enumerator = zfile.entries();
|
||||||
|
while (enumerator.hasMoreElements()) {
|
||||||
|
ZipEntry entry = enumerator.nextElement();
|
||||||
|
String path = entry.getName();
|
||||||
|
if (path.endsWith(".java")) {
|
||||||
|
removeDeadCode(path, getSource(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
zfile.close(); // Also closes input stream.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Options.insertSourcePath(0, Options.getTemporaryDirectory());
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String removeDeadCode(String path, String source) throws IOException {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
int beginningErrorLevel = getCurrentErrorLevel();
|
||||||
|
|
||||||
|
// Parse and resolve source
|
||||||
|
CompilationUnit unit = parse(path, source);
|
||||||
|
if (getCurrentErrorLevel() > beginningErrorLevel) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
initializeTranslation(unit);
|
||||||
|
String newSource = removeDeadCode(unit, source);
|
||||||
|
if (!newSource.equals(source)) {
|
||||||
|
// Save the new source to the tmpdir and update the files list.
|
||||||
|
String pkg = unit.getPackage().getName().getFullyQualifiedName();
|
||||||
|
File packageDir = new File(Options.getTemporaryDirectory(),
|
||||||
|
pkg.replace('.', File.separatorChar));
|
||||||
|
packageDir.mkdirs();
|
||||||
|
int index = path.lastIndexOf(File.separatorChar);
|
||||||
|
String outputName = index >= 0 ? path.substring(index + 1) : path;
|
||||||
|
File outFile = new File(packageDir, outputName);
|
||||||
|
Files.write(newSource, outFile, Charsets.UTF_8);
|
||||||
|
path = outFile.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||||
|
if (logger.getLevel().intValue() <= Level.FINE.intValue()) {
|
||||||
|
System.out.println(
|
||||||
|
String.format("dead-code elimination time: %.3f", inSeconds(elapsedTime)));
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates a parsed source file, modifying the compilation unit by
|
||||||
|
* substituting core Java type and method references with iOS equivalents.
|
||||||
|
* For example, <code>java.lang.Object</code> maps to <code>NSObject</code>,
|
||||||
|
* and <code>java.lang.String</code> to <code>NSString</code>. The source is
|
||||||
|
* also modified to add support for iOS memory management, extract inner
|
||||||
|
* classes, etc.
|
||||||
|
* <p>
|
||||||
|
* Note: the returned source file doesn't need to be re-parsed, since the
|
||||||
|
* compilation unit already reflects the changes (it's useful, though,
|
||||||
|
* for dumping intermediate stages).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param unit the compilation unit created by ASTParser
|
||||||
|
* @param source the Java source used by ASTParser
|
||||||
|
* @return the rewritten source
|
||||||
|
* @throws AssertionError if the translator makes invalid edits
|
||||||
|
*/
|
||||||
|
public static String translate(CompilationUnit unit, String source) {
|
||||||
|
|
||||||
|
// Update code that has GWT references.
|
||||||
|
new GwtConverter().run(unit);
|
||||||
|
|
||||||
|
// Modify AST to be more compatible with Objective C
|
||||||
|
new Rewriter().run(unit);
|
||||||
|
|
||||||
|
// Add auto-boxing conversions.
|
||||||
|
new Autoboxer(unit.getAST()).run(unit);
|
||||||
|
|
||||||
|
// Normalize init statements
|
||||||
|
new InitializationNormalizer().run(unit);
|
||||||
|
|
||||||
|
// Extract inner and anonymous classes
|
||||||
|
new AnonymousClassConverter(unit).run(unit);
|
||||||
|
new InnerClassExtractor(unit).run(unit);
|
||||||
|
|
||||||
|
// Translate core Java type use to similar iOS types
|
||||||
|
new JavaToIOSTypeConverter().run(unit);
|
||||||
|
Map<String, String> methodMappings = Options.getMethodMappings();
|
||||||
|
if (methodMappings.isEmpty()) {
|
||||||
|
// Method maps are loaded here so tests can call translate() directly.
|
||||||
|
loadMappingFiles();
|
||||||
|
}
|
||||||
|
new JavaToIOSMethodTranslator(unit.getAST(), methodMappings).run(unit);
|
||||||
|
|
||||||
|
// Add dealloc/finalize method(s), if necessary. This is done
|
||||||
|
// after inner class extraction, so that each class releases
|
||||||
|
// only its own instance variables.
|
||||||
|
new DestructorGenerator().run(unit);
|
||||||
|
|
||||||
|
for (Plugin plugin : Options.getPlugins()) {
|
||||||
|
plugin.processUnit(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all modified nodes have type bindings
|
||||||
|
Types.verifyNode(unit);
|
||||||
|
|
||||||
|
Document doc = new Document(source);
|
||||||
|
TextEdit edit = unit.rewrite(doc, Options.getCompilerOptions());
|
||||||
|
try {
|
||||||
|
edit.apply(doc);
|
||||||
|
} catch (MalformedTreeException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (BadLocationException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
return doc.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initializeTranslation(CompilationUnit unit) {
|
||||||
|
unit.recordModifications();
|
||||||
|
NameTable.initialize(unit);
|
||||||
|
Types.initialize(unit);
|
||||||
|
Symbols.initialize(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveConvertedSource(String filename, String content) {
|
||||||
|
try {
|
||||||
|
File outputFile = new File(Options.getOutputDirectory(), filename);
|
||||||
|
outputFile.getParentFile().mkdirs();
|
||||||
|
Files.write(content, outputFile, Charset.defaultCharset());
|
||||||
|
} catch (IOException e) {
|
||||||
|
error(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setPaths(ASTParser parser) {
|
||||||
|
// Add existing boot classpath after declared path, so that core files
|
||||||
|
// referenced, but not being translated, are included. This only matters
|
||||||
|
// when compiling the JRE emulation library sources.
|
||||||
|
List<String> fullClasspath = Lists.newArrayList();
|
||||||
|
String[] classpathEntries = Options.getClassPathEntries();
|
||||||
|
for (int i = 0; i < classpathEntries.length; i++) {
|
||||||
|
fullClasspath.add(classpathEntries[i]);
|
||||||
|
}
|
||||||
|
String bootclasspath = Options.getBootClasspath();
|
||||||
|
for (String path : bootclasspath.split(":")) {
|
||||||
|
// JDT requires that all path elements exist and can hold class files.
|
||||||
|
File f = new File(path);
|
||||||
|
if (f.exists() && (f.isDirectory() || path.endsWith(".jar"))) {
|
||||||
|
fullClasspath.add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parser.setEnvironment(fullClasspath.toArray(new String[0]), Options.getSourcePathEntries(),
|
||||||
|
null, true);
|
||||||
|
|
||||||
|
// Workaround for ASTParser.setEnvironment() bug, which ignores its
|
||||||
|
// last parameter. This has been fixed in the Eclipse post-3.7 Java7
|
||||||
|
// branch.
|
||||||
|
try {
|
||||||
|
Field field = parser.getClass().getDeclaredField("bits");
|
||||||
|
field.setAccessible(true);
|
||||||
|
int bits = field.getInt(parser);
|
||||||
|
// Turn off CompilationUnitResolver.INCLUDE_RUNNING_VM_BOOTCLASSPATH
|
||||||
|
bits &= ~0x20;
|
||||||
|
field.setInt(parser, bits);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// should never happen, since only the one known class is manipulated
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSource(String path) throws IOException {
|
||||||
|
File file = findSourceFile(path);
|
||||||
|
if (file == null) {
|
||||||
|
return findArchivedSource(path);
|
||||||
|
} else {
|
||||||
|
return Files.toString(file, Charset.defaultCharset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File findSourceFile(String filename) {
|
||||||
|
File f = getFileOrNull(filename);
|
||||||
|
if (f != null) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
for (String pathEntry : Options.getSourcePathEntries()) {
|
||||||
|
f = getFileOrNull(pathEntry + File.separatorChar + filename);
|
||||||
|
if (f != null) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findArchivedSource(String path) throws IOException {
|
||||||
|
for (String pathEntry : Options.getSourcePathEntries()) {
|
||||||
|
File f = new File(pathEntry);
|
||||||
|
if (f.exists() && f.isFile()) {
|
||||||
|
ZipFile zfile;
|
||||||
|
try {
|
||||||
|
zfile = new ZipFile(f);
|
||||||
|
} catch (ZipException e) {
|
||||||
|
// Not a zip or jar file, so skip it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ZipEntry entry = zfile.getEntry(path);
|
||||||
|
if (entry == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Reader in = new InputStreamReader(zfile.getInputStream(entry));
|
||||||
|
return CharStreams.toString(in);
|
||||||
|
} finally {
|
||||||
|
zfile.close(); // Also closes input stream.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getFileOrNull(String fileName) {
|
||||||
|
File f = new File(fileName);
|
||||||
|
return f.exists() ? f : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void translateSourceJar(J2ObjC compiler, String jarPath) throws IOException {
|
||||||
|
File f = new File(jarPath);
|
||||||
|
if (f.exists() && f.isFile()) {
|
||||||
|
ZipFile zfile = new ZipFile(f);
|
||||||
|
try {
|
||||||
|
Enumeration<? extends ZipEntry> enumerator = zfile.entries();
|
||||||
|
while (enumerator.hasMoreElements()) {
|
||||||
|
ZipEntry entry = enumerator.nextElement();
|
||||||
|
String path = entry.getName();
|
||||||
|
if (path.endsWith(".java")) {
|
||||||
|
printInfo("translating " + path);
|
||||||
|
compiler.translate(path);
|
||||||
|
nFiles++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ZipException e) {
|
||||||
|
// Not a zip or jar file, so skip it.
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
zfile.close(); // Also closes input stream.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an error during translation.
|
||||||
|
*/
|
||||||
|
public static void error(String message) {
|
||||||
|
System.err.println("error: " + message);
|
||||||
|
error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the error counter, but don't display an error diagnostic.
|
||||||
|
* This should only be directly called via tests that are testing
|
||||||
|
* error conditions.
|
||||||
|
*/
|
||||||
|
public static void error() {
|
||||||
|
nErrors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an ASTVisitor error.
|
||||||
|
*/
|
||||||
|
public static void error(ASTNodeException e) {
|
||||||
|
System.err.println(String.format("Internal error, translating %s, line %d\nStack trace:",
|
||||||
|
currentFileName, currentUnit.getLineNumber(e.getSourcePosition())));
|
||||||
|
nErrors++;
|
||||||
|
e.getCause().printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report a warning during translation.
|
||||||
|
*/
|
||||||
|
public static void warning(String message) {
|
||||||
|
System.err.println("warning: " + message);
|
||||||
|
if (Options.treatWarningsAsErrors()) {
|
||||||
|
nErrors++;
|
||||||
|
} else {
|
||||||
|
nWarnings++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an error with a specific AST node.
|
||||||
|
*/
|
||||||
|
public static void error(ASTNode node, String message) {
|
||||||
|
int line = getNodeLine(node);
|
||||||
|
error(String.format("%s:%s: %s", currentFileName, line, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report a warning with a specific AST node.
|
||||||
|
*/
|
||||||
|
public static void warning(ASTNode node, String message) {
|
||||||
|
int line = getNodeLine(node);
|
||||||
|
warning(String.format("%s:%s: %s", currentFileName, line, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCurrentErrorLevel() {
|
||||||
|
return Options.treatWarningsAsErrors() ? nErrors + nWarnings : nErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getNodeLine(ASTNode node) {
|
||||||
|
CompilationUnit unit = (CompilationUnit) node.getRoot();
|
||||||
|
return unit.getLineNumber(node.getStartPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loadDeadCodeMap() {
|
||||||
|
DeadCodeMap map = null;
|
||||||
|
File file = Options.getProGuardUsageFile();
|
||||||
|
if (file != null) {
|
||||||
|
try {
|
||||||
|
map = ProGuardUsageParser.parse(Files.newReaderSupplier(file, Charset.defaultCharset()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Options.setDeadCodeMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loadMappingFiles() {
|
||||||
|
for (String resourceName : Options.getMappingFiles()) {
|
||||||
|
Properties mappings = new Properties();
|
||||||
|
try {
|
||||||
|
mappings.load(J2ObjC.class.getResourceAsStream(resourceName));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Enumeration<?> keyIterator = mappings.propertyNames();
|
||||||
|
while (keyIterator.hasMoreElements()) {
|
||||||
|
String javaMethod = (String) keyIterator.nextElement();
|
||||||
|
String iosMethod = mappings.getProperty(javaMethod);
|
||||||
|
Options.getMethodMappings().put(javaMethod, iosMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static void reset() {
|
||||||
|
nErrors = 0;
|
||||||
|
nWarnings = 0;
|
||||||
|
nFiles = 0;
|
||||||
|
currentFileName = null;
|
||||||
|
currentUnit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getErrorCount() {
|
||||||
|
return nErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getWarningCount() {
|
||||||
|
return nWarnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void exit() {
|
||||||
|
printInfo(String.format("Translated %d %s: %d errors, %d warnings",
|
||||||
|
nFiles, nFiles == 1 ? "file" : "files", nErrors, nWarnings));
|
||||||
|
Options.deleteTemporaryDirectory();
|
||||||
|
System.exit(nErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printInfo(String msg) {
|
||||||
|
if (logger.getLevel().intValue() <= Level.INFO.intValue()) {
|
||||||
|
System.out.println(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints time spent in each translation step. Values are in milliseconds, but displayed
|
||||||
|
* as fractional seconds.
|
||||||
|
*/
|
||||||
|
private static void printTimingInfo(long read, long compile, long translate,
|
||||||
|
long write, long total) {
|
||||||
|
if (logger.getLevel().intValue() <= Level.FINE.intValue()) {
|
||||||
|
System.out.println(
|
||||||
|
String.format("time: read=%.3f compile=%.3f translate=%.3f write=%.3f total=%.3f",
|
||||||
|
inSeconds(read), inSeconds(compile), inSeconds(translate),
|
||||||
|
inSeconds(write), inSeconds(total)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float inSeconds(long milliseconds) {
|
||||||
|
return (float) milliseconds / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFileHeader(String sourceFileName) {
|
||||||
|
// Template parameters are: source file, user name, date.
|
||||||
|
String username = System.getProperty("user.name");
|
||||||
|
Date now = new Date();
|
||||||
|
String generationDate = DateFormat.getDateInstance(DateFormat.SHORT).format(now);
|
||||||
|
return String.format(Options.getFileHeader(), sourceFileName, username, generationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class JarFileLoader extends URLClassLoader {
|
||||||
|
public JarFileLoader() {
|
||||||
|
super(new URL[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addJarFile(String path) throws MalformedURLException {
|
||||||
|
String urlPath = "jar:file://" + path + "!/";
|
||||||
|
addURL(new URL(urlPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initPlugins(String[] pluginPaths, String pluginOptionString)
|
||||||
|
throws IOException {
|
||||||
|
JarFileLoader classLoader = new JarFileLoader();
|
||||||
|
for (String path : pluginPaths) {
|
||||||
|
if (path.endsWith(".jar")) {
|
||||||
|
JarInputStream jarStream = new JarInputStream(new FileInputStream(path));
|
||||||
|
classLoader.addJarFile(new File(path).getAbsolutePath());
|
||||||
|
|
||||||
|
JarEntry entry;
|
||||||
|
while ((entry = jarStream.getNextJarEntry()) != null) {
|
||||||
|
String entryName = entry.getName();
|
||||||
|
if (!entryName.endsWith(".class")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String className =
|
||||||
|
entryName.replaceAll("/", "\\.").substring(0, entryName.length() - ".class".length());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> clazz = classLoader.loadClass(className);
|
||||||
|
if (Plugin.class.isAssignableFrom(clazz)) {
|
||||||
|
Constructor<?> cons = clazz.getDeclaredConstructor();
|
||||||
|
Plugin plugin = (Plugin) cons.newInstance();
|
||||||
|
plugin.initPlugin(pluginOptionString);
|
||||||
|
Options.getPlugins().add(plugin);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("plugin exception: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warning("Don't understand plugin path entry: " + path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void error(Exception e) {
|
||||||
|
logger.log(Level.SEVERE, "Exiting due to exception", e);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for tool.
|
||||||
|
*
|
||||||
|
* @param args command-line arguments: flags and source file names
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String[] files = null;
|
||||||
|
try {
|
||||||
|
files = Options.load(args);
|
||||||
|
} catch (IOException e) {
|
||||||
|
error(e.getMessage());
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
J2ObjC compiler = new J2ObjC();
|
||||||
|
|
||||||
|
try {
|
||||||
|
initPlugins(Options.getPluginPathEntries(), Options.getPluginOptionString());
|
||||||
|
} catch (IOException e) {
|
||||||
|
error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove dead-code first, so modified file paths are replaced in the
|
||||||
|
// translation list.
|
||||||
|
int beginningErrorLevel = compiler.getCurrentErrorLevel();
|
||||||
|
try {
|
||||||
|
files = compiler.removeDeadCode(files);
|
||||||
|
} catch (IOException e) {
|
||||||
|
error(e.getMessage());
|
||||||
|
}
|
||||||
|
if (compiler.getCurrentErrorLevel() > beginningErrorLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nFiles = 0;
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
String file = files[i];
|
||||||
|
try {
|
||||||
|
if (file.endsWith(".java")) { // Eclipse may send all project entities.
|
||||||
|
printInfo("translating " + file);
|
||||||
|
compiler.translate(file);
|
||||||
|
nFiles++;
|
||||||
|
} else if (file.endsWith(".jar")) {
|
||||||
|
translateSourceJar(compiler, file);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
error(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Plugin plugin : Options.getPlugins()) {
|
||||||
|
plugin.endProcessing(Options.getOutputDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue