/*
* Copyright (C) 2014 Civilian Framework.
*
* Licensed under the Civilian License (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.civilian-framework.org/license.txt
*
* 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 org.civilian.tool.resbundle;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.civilian.internal.source.OutputLocation;
import org.civilian.internal.source.PackageDetector;
import org.civilian.template.TemplateWriter;
import org.civilian.text.msg.MsgId;
import org.civilian.util.Arguments;
import org.civilian.util.DateTime;
import org.civilian.util.FileType;
import org.civilian.util.IoUtil;
import org.civilian.util.StringUtil;
/**
* ResBundleCompiler allows you to store all your localized message texts
* in an excel file. The compiler can then generate the resource
* bundle property files from the excel file. It can also generate
* an additional class which defines constants for the message ids.
* The ResBundleCompiler uses Apache POI to read the excel file.
*/
public class ResBundleCompiler
{
private static final String PROPERTIES_ENCODING = "ISO-8859-1";
private static final String DEFAULT_JAVA_ENCODING = "UTF-8";
/**
* Command line entry point.
*/
public static void main(String[] stringArgs) throws IOException
{
Arguments args = new Arguments(stringArgs);
if (!args.hasMore() || args.consume("-?") || args.consume("-help"))
{
printHelp();
return;
}
Config config = new Config();
while(args.startsWith("-"))
{
if (args.consume("-constClass"))
{
config.constClass = args.next("constClass");
}
else if (args.consume("-idClass"))
{
config.idClass = args.next("idClass");
if ("#msgId".equals(config.idClass))
{
config.idClass = MsgId.class.getName();
config.inlineIdClass = false;
}
else if ("#inline".equals(config.idClass))
{
config.idClass = "Id";
config.inlineIdClass = true;
}
}
else if (args.consume("-enc"))
config.encoding = args.next("encoding");
else if (args.startsWith("-out:"))
config.outputType = OutputLocation.parse(args, true, false);
else if (args.consume("-v"))
config.verbose = true;
else
throw new IllegalArgumentException("unknown option " + args.next());
}
config.excelFile = args.nextFile("excel message file", FileType.EXISTENT_FILE);
if ("#file".equals(config.constClass))
config.constClass = StringUtil.startUpperCase(IoUtil.cutExtension(config.excelFile));
ResBundleCompiler c = new ResBundleCompiler();
c.run(config);
}
/**
* Prints a help text.
*/
private static void printHelp()
{
System.out.println("usage:");
System.out.println("java " + ResBundleCompiler.class.getName() + " [<parameter>]* <excel>");
System.out.println();
System.out.println("parameters: default:");
System.out.println("-constClass <class> generate class for message id constants");
System.out.println(" use #file to name the class like the excel file");
System.out.println("-enc <encoding> encoding of generated Java files " + DEFAULT_JAVA_ENCODING);
System.out.println("-idClass <class> use the class instead of string message id");
System.out.println(" use #msgId to use org.civilian.text.msg.MsgId");
System.out.println(" use #inline to generate a inline id class");
System.out.println("-v print progress");
OutputLocation.printHelp(false);
}
/**
* Runs the compiler.
*/
public boolean run(Config config) throws IOException
{
config_ = config;
generationTime_ = new DateTime();
log("reading excel text definition " + config_.excelFile);
reader_ = new ExcelIterator(config_.excelFile);
if (!reader_.inputOk)
{
log("error: the input file does not seem to be a excel file");
return false;
}
String packageName = null;
if (config.outputType.needsPackage() || (config.constClass != null))
packageName = PackageDetector.detectPackage(config.excelFile);
createLanguageOutputs(packageName, config_.excelFile);
log("compiling");
ArrayList<String> msgIds = compileBundles();
if (config.constClass != null)
{
File constantsFile = config_.outputType.getOutputFile(packageName, config.constClass + ".java", config.excelFile).file;
constantsOutput_ = new Output(constantsFile);
compileConstants(packageName, msgIds);
}
if (constantsOutput_ != null)
constantsOutput_.writeToFile(config_.encoding);
for (Output langOutput : langOutputs_)
langOutput.writeToFile(PROPERTIES_ENCODING);
log("done");
return true;
}
private void createLanguageOutputs(String packageName, File excelFile) throws IOException
{
log("read languages");
if (!reader_.nextLine())
throw new IOException(config_.excelFile + " is empty");
reader_.nextString(); // header of keycol
ArrayList<Output> list = new ArrayList<>();
String filePrefix = IoUtil.cutExtension(config_.excelFile) + "_";
String language;
while((language = reader_.nextString()) != null)
{
log("- add language " + language);
String langFileName = filePrefix + language + ".properties";
File langFile = config_.outputType.getOutputFile(packageName, langFileName, excelFile).file;
list.add(new Output(langFile));
}
list.toArray(langOutputs_ = new Output[list.size()]);
}
private ArrayList<String> compileBundles()
{
ArrayList<String> ids = new ArrayList<>();
for (int i=0; i<langOutputs_.length; i++)
{
TemplateWriter out = langOutputs_[i].out;
out.println("# Generated from " + config_.excelFile.getName() + " at " + generationTime_);
out.println("# Do not edit directly.");
out.println("# Encoding is " + PROPERTIES_ENCODING);
}
while(reader_.nextLine())
{
String id = reader_.nextString();
if (id == null)
continue;
id = id.trim();
if (id.length() == 0)
continue;
ids.add(id);
for (int i=0; i<langOutputs_.length; i++)
{
String text = reader_.nextString();
if (text == null)
text = "?" + id;
else
text = text.trim();
TemplateWriter out = langOutputs_[i].out;
out.print(id);
out.print("=");
int len = text.length();
for (int j=0; j<len; j++)
{
char c = text.charAt(j);
if (c <= 255)
out.print(c);
else
{
out.print("\\u");
out.print(StringUtil.fillLeft(Integer.toHexString(c), 4, '0'));
}
}
out.println();
}
}
return ids;
}
private void compileConstants(String packageName, ArrayList<String> ids)
{
Collections.sort(ids);
ConstClassTemplate t = new ConstClassTemplate(config_, packageName, generationTime_, ids);
t.print(constantsOutput_.out);
}
private void log(String message)
{
if (config_.verbose)
System.out.println(message);
}
static class Config
{
public OutputLocation outputType = OutputLocation.OUTPUT_TO_INPUT_DIR;
public String encoding = DEFAULT_JAVA_ENCODING;
public File excelFile;
public boolean inlineIdClass;
public String idClass;
public String constClass;
public boolean verbose;
}
private class Output
{
public Output(File file)
{
this.file = file;
this.out = new TemplateWriter(stringOut = new StringWriter());
}
public void writeToFile(String encoding) throws IOException
{
out.flush();
log("writing " + file.getAbsolutePath());
File parent = file.getParentFile();
IoUtil.mkdirs(parent);
Writer fileOut = new OutputStreamWriter(new FileOutputStream(file), encoding);
try
{
fileOut.write(stringOut.toString());
}
finally
{
fileOut.close();
}
}
public final File file;
public TemplateWriter out;
private StringWriter stringOut;
}
private static class ExcelIterator
{
public ExcelIterator(File file) throws IOException
{
try
{
Workbook wb = WorkbookFactory.create(file);
sheet_ = wb.getSheetAt(0);
nextRow_ = sheet_.getFirstRowNum();
inputOk = true;
}
catch(InvalidFormatException e)
{
inputOk = false;
}
catch(IOException e)
{
if (e.getMessage().startsWith("Invalid header signature"))
inputOk = false;
else
throw e;
}
}
public boolean nextLine()
{
nextCell_ = 0;
row_ = sheet_.getRow(nextRow_++);
return row_ != null;
}
public String nextString()
{
return nextString(null);
}
public String nextString(String defaultValue)
{
Cell cell = row_.getCell(nextCell_++);
try
{
if ((cell != null) && (cell.getCellType() == Cell.CELL_TYPE_STRING))
return cell.getStringCellValue();
else
return defaultValue;
}
catch(RuntimeException e)
{
System.err.println("exception in row " + nextRow_ + ", cell " + nextCell_);
throw e;
}
}
private Sheet sheet_;
private Row row_;
private int nextRow_;
private int nextCell_;
public boolean inputOk;
}
private Config config_;
private Output constantsOutput_;
private ExcelIterator reader_;
private Output[] langOutputs_;
private DateTime generationTime_;
}