/* * 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.pipeline; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.io.Files; import com.google.devtools.j2objc.Options; import com.google.devtools.j2objc.file.InputFile; import com.google.devtools.j2objc.file.RegularInputFile; import com.google.devtools.j2objc.gen.GenerationUnit; import com.google.devtools.j2objc.util.ErrorUtil; import com.google.devtools.j2objc.util.FileUtil; import java.io.File; import java.io.IOException; import java.util.Enumeration; import java.util.List; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; /** * A set of input files for J2ObjC to process, * together with behavior for scanning input files and adding more files. * This class also contains a queue that can be used by processors that dynamically * add more files while they process. * * @author Tom Ball, Keith Stanger, Mike Thvedt */ public class GenerationBatch { private static final Logger logger = Logger.getLogger(GenerationBatch.class.getName()); private static final String J2OBJC_TEMP_DIR_PREFIX = "J2ObjCTempDir"; private final Options options; private final List<ProcessingContext> inputs = Lists.newArrayList(); public GenerationBatch(Options options){ this.options = options; } public List<ProcessingContext> getInputs() { return inputs; } public void processFileArgs(Iterable<String> args) { for (String arg : args) { processFileArg(arg); } } public void processFileArg(String arg) { if (arg.startsWith("@")) { processManifestFile(arg.substring(1)); } else { processSourceFile(arg); } } private void processManifestFile(String filename) { if (filename.isEmpty()) { ErrorUtil.error("no @ file specified"); return; } File f = new File(filename); if (!f.exists()) { ErrorUtil.error("no such file: " + filename); return; } try { String fileList = Files.toString(f, options.fileUtil().getCharset()); if (fileList.isEmpty()) { return; } String[] files = fileList.split("\\s+"); // Split on any whitespace. for (String file : files) { processSourceFile(file); } } catch (IOException e) { ErrorUtil.error(e.getMessage()); } } private void processSourceFile(String filename) { logger.finest("processing " + filename); if (filename.endsWith(".java")) { processJavaFile(filename); } else { processJarFile(filename); } } private void processJavaFile(String filename) { InputFile inputFile; try { inputFile = new RegularInputFile(filename, filename); if (!inputFile.exists()) { // Convert to a qualified name and search on the sourcepath. String qualifiedName = filename.substring(0, filename.length() - 5).replace(File.separatorChar, '.'); inputFile = options.fileUtil().findOnSourcePath(qualifiedName); if (inputFile == null) { ErrorUtil.error("No such file: " + filename); return; } } } catch (IOException e) { ErrorUtil.warning(e.getMessage()); return; } addSource(inputFile); } private File findJarFile(String filename) { File f = new File(filename); if (f.exists() && f.isFile()) { return f; } // Checking the sourcepath is helpful for our unit tests where the source // jars aren't relative to the current working directory. for (String path : options.fileUtil().getSourcePathEntries()) { File dir = new File(path); if (dir.isDirectory()) { f = new File(dir, filename); if (f.exists() && f.isFile()) { return f; } } } return null; } private void processJarFile(String filename) { File f = findJarFile(filename); if (f == null) { ErrorUtil.error("No such file: " + filename); return; } // Warn if source debugging is specified for a jar file, since native debuggers // don't support Java-like source paths. if (options.emitLineDirectives()) { ErrorUtil.warning("source debugging of jar files is not supported: " + filename); } GenerationUnit combinedUnit = null; if (options.getHeaderMap().combineSourceJars()) { combinedUnit = GenerationUnit.newCombinedJarUnit(filename, options); } try { ZipFile zfile = new ZipFile(f); try { Enumeration<? extends ZipEntry> enumerator = zfile.entries(); File tempDir = FileUtil.createTempDir(J2OBJC_TEMP_DIR_PREFIX); String tempDirPath = tempDir.getAbsolutePath(); options.fileUtil().addTempDir(tempDirPath); options.fileUtil().appendSourcePath(tempDirPath); while (enumerator.hasMoreElements()) { ZipEntry entry = enumerator.nextElement(); String internalPath = entry.getName(); if (internalPath.endsWith(".java")) { // Extract JAR file to a temporary directory File outputFile = options.fileUtil().extractZipEntry(tempDir, zfile, entry); InputFile newFile = new RegularInputFile(outputFile.getAbsolutePath(), internalPath); if (combinedUnit != null) { inputs.add(new ProcessingContext(newFile, combinedUnit)); } else { addExtractedJarSource(newFile, filename, internalPath); } } } } finally { zfile.close(); // Also closes input stream. } } catch (ZipException e) { // Also catches JarExceptions logger.fine(e.getMessage()); ErrorUtil.error("Error reading file " + filename + " as a zip or jar file."); } catch (IOException e) { ErrorUtil.error(e.getMessage()); } } private void addExtractedJarSource(InputFile file, String jarFileName, String internalPath) { String sourceName = "jar:file:" + jarFileName + "!" + internalPath; inputs.add(ProcessingContext.fromExtractedJarEntry(file, sourceName, options)); } /** * Adds the given InputFile to this GenerationBatch, * creating GenerationUnits and inferring unit names/output paths as necessary. */ @VisibleForTesting public void addSource(InputFile file) { inputs.add(ProcessingContext.fromFile(file, options)); } }