/*
* Copyright (C) 2011.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
package uk.me.parabola.mkgmap.main;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.typ.TYPFile;
import uk.me.parabola.imgfmt.app.typ.TypData;
import uk.me.parabola.imgfmt.app.typ.TypLabelException;
import uk.me.parabola.imgfmt.app.typ.TypParam;
import uk.me.parabola.imgfmt.sys.FileImgChannel;
import uk.me.parabola.mkgmap.CommandArgs;
import uk.me.parabola.mkgmap.scan.SyntaxException;
import uk.me.parabola.mkgmap.typ.TypTextReader;
/**
* Standalone program to compile a TYP file from the text format.
* Simple main program to demonstrate compiling a typ.txt file.
*
* Usage: TypTextReader [in-file] [out-file]
*
* in-file defaults to 'default.txt'
* out-file defaults to 'OUT.TYP'
*
*/
public class TypCompiler implements MapProcessor {
/**
* The integration with mkgmap.
*
* @param args The options that are in force.
* @param filename The input filename.
* @return Returns the name of the file that was written. It depends on the family id.
*/
public String makeMap(CommandArgs args, String filename) {
assert filename.toLowerCase().endsWith(".txt");
CharsetProbe probe = new CharsetProbe();
String readCharset = probe.probeCharset(filename);
TypData data;
try {
data = compile(filename, readCharset, args.getSort());
} catch (SyntaxException e) {
throw new MapFailedException("Compiling TYP txt file: " + e.getMessage());
} catch (FileNotFoundException e) {
throw new MapFailedException("Could not open TYP file " + filename + " to read");
}
TypParam param = data.getParam();
int family = args.get("family-id", -1);
int product = args.get("product-id", -1);
int cp = args.get("code-page", -1);
if (family != -1)
param.setFamilyId(family);
if (product != -1)
param.setProductId(product);
if (cp != -1)
param.setCodePage(cp);
File outFile = new File(filename);
String outName = outFile.getName();
int last;
if (outName.length() > 4 && (last = outName.lastIndexOf('.')) > 0)
outName = outName.substring(0, last);
outName += ".typ";
outFile = new File(args.getOutputDir(), outName);
try {
writeTyp(data, outFile);
} catch (TypLabelException e) {
throw new MapFailedException("TYP file cannot be written in code page "
+ data.getSort().getCodepage());
} catch (IOException e) {
throw new MapFailedException("Error while writing typ file", e);
}
return outFile.getPath();
}
/**
* Read and compile a TYP file, returning the compiled form.
*
* @param filename The input filename.
* @param charset The character set to use to read this file. We should have already determined
* that this character set is valid and can be used to read the file.
* @param sort The sort information from command line options, used for the output code page
* only. If null, then the code page set by CodePage in the typ.txt file will be used.
*
* @return The compiled form as a data structure.
* @throws FileNotFoundException If the file doesn't exist.
* @throws SyntaxException All user correctable problems in the input file.
*/
private TypData compile(String filename, String charset, Sort sort)
throws FileNotFoundException, SyntaxException
{
TypTextReader tr = new TypTextReader();
TypData data = tr.getData();
data.setSort(sort);
try {
Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(filename), charset));
tr.read(filename, r);
} catch (UnsupportedEncodingException e) {
// Not likely to happen as we should have already used this character set!
throw new MapFailedException("Unsupported character set", e);
}
return tr.getData();
}
/**
* Write the type file out from the compiled form to the given name.
*/
private void writeTyp(TypData data, File file) throws IOException {
RandomAccessFile raf = new RandomAccessFile(file, "rw");
FileChannel channel = raf.getChannel();
channel.truncate(0);
FileImgChannel w = new FileImgChannel(channel);
TYPFile typ = new TYPFile(w);
typ.setData(data);
typ.write();
typ.close();
}
/**
* Simple standalone compiler.
*
* Usage: TypCompiler [in-file] [out-file]
* in-file defaults to 'default.txt'
* out-file defaults to OUT.TYP
*/
public static void main(String[] args) throws IOException {
String in = "default.txt";
if (args.length > 0)
in = args[0];
String out = "OUT.TYP";
if (args.length > 1)
out = args[1];
new TypCompiler().standAloneRun(in, out);
}
private void standAloneRun(String in, String out) {
CharsetProbe probe = new CharsetProbe();
String readCharset = probe.probeCharset(in);
TypData data;
try {
data = compile(in, readCharset, null);
} catch (SyntaxException e) {
System.out.println(e.getMessage());
return;
} catch (FileNotFoundException e) {
throw new MapFailedException("Could not open TYP file " + in + " to read");
}
try {
writeTyp(data, new File(out));
} catch (IOException e) {
System.out.println("Error writing file: " + e.getMessage());
}
}
class CharsetProbe {
private String codePage;
private CharsetEncoder encoder;
public CharsetProbe() {
setCodePage("latin1");
}
private void setCodePage(String codePage) {
this.codePage = codePage;
this.encoder = Charset.forName(codePage).newEncoder();
}
private String probeCharset(String file) {
String readingCharset = "utf-8";
try {
tryCharset(file, readingCharset);
return readingCharset;
} catch (TypLabelException e) {
try {
readingCharset = e.getCharsetName();
tryCharset(file, readingCharset);
} catch (Exception e1) {
return "utf-8";
}
}
return readingCharset;
}
private void tryCharset(String file, String readingCharset) {
InputStream is = null;
try {
is = new FileInputStream(file);
BufferedReader br = new BufferedReader(new InputStreamReader(is, readingCharset));
String line;
while ((line = br.readLine()) != null) {
if (line.isEmpty())
continue;
// This is a giveaway the file is in utf-something, so ignore anything else
if (line.charAt(0) == 0xfeff)
return;
if (line.startsWith("CodePage")) {
String[] split = line.split("=");
try {
setCodePage("cp" + Integer.decode(split[1].trim()));
} catch (NumberFormatException e) {
setCodePage("cp1252");
}
}
if (line.startsWith("String")) {
CharBuffer cb = CharBuffer.wrap(line);
if (encoder != null)
encoder.encode(cb);
}
}
} catch (UnsupportedEncodingException e) {
throw new TypLabelException(codePage);
} catch (CharacterCodingException e) {
throw new TypLabelException(codePage);
} catch (FileNotFoundException e) {
throw new ExitException("File not found " + file);
} catch (IOException e) {
throw new ExitException("Could not read file " + file);
} finally {
Utils.closeFile(is);
}
}
}
}