Browse Source

initial git commit

master
Kin-Wai Koo 13 years ago
commit
3641e2586a
  1. 22
      LICENSE.txt
  2. 41
      README.txt
  3. 175
      src/cli/WhisperPrep.java
  4. 49
      src/gui/CustomJTable.java
  5. 8
      src/gui/EXTHAddRecordListener.java
  6. 14
      src/gui/GuiException.java
  7. 201
      src/gui/GuiModel.java
  8. 73
      src/gui/HeaderInfoDialog.java
  9. 74
      src/gui/LanguageCodes.java
  10. 258
      src/gui/LanguageDialog.java
  11. 12
      src/gui/LanguageModel.java
  12. 667
      src/gui/Main.java
  13. 6
      src/gui/MetaInfoProvider.java
  14. 32
      src/gui/MobiFileFilter.java
  15. 175
      src/gui/NewRecordDialog.java
  16. 185
      src/mobimeta/EXTHHeader.java
  17. 251
      src/mobimeta/EXTHRecord.java
  18. 15
      src/mobimeta/MobiCommon.java
  19. 526
      src/mobimeta/MobiHeader.java
  20. 292
      src/mobimeta/MobiMeta.java
  21. 14
      src/mobimeta/MobiMetaException.java
  22. 164
      src/mobimeta/PDBHeader.java
  23. 41
      src/mobimeta/RecordInfo.java
  24. 183
      src/mobimeta/StreamUtils.java

22
LICENSE.txt

@ -0,0 +1,22 @@
Copyright (c) 2012 Kin-Wai Koo
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the ÒSoftwareÓ), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ÒAS ISÓ, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

41
README.txt

@ -0,0 +1,41 @@
You will need to have Java (JDK or JRE) 1.5 or above to execute this program.
To start the program, just double-click on the MobiMetaEditorV0.15.jar file.
If that doesn't work, type this from the command-line:
java -jar MobiMetaEditorV0.15.jar
This was written based on the MOBI file format describe in:
http://wiki.mobileread.com/wiki/MOBI
This application is licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php).
ChangeLog
v0.15
- added the ability to add/edit the TTS EXTH record
v0.14
- does not pack the header if the full name field and EXTH records remain unchanged
- added Open and Save menu items
- lets the user specify the target filename
v0.13
- added a Header Info dialog, which details various fields in the PDB header, PalmDOC header, and MOBI header
- added in provisions to invoke debug and safe modes from the command line
- added in WhisperPrep, which lets users set ASINs and sets the CDEType to EBOK
(to invoke, type "java -cp MobiMetaEditorV0.13.jar cli.WhisperPrep
<input directory> <output directory>")
v0.12
- changed the GUI to use FileDialog instead of JFileChooser for a more native look and feel
- added support for window modified indicator on OS X
- lets the user specify an input filename on the command line
v0.11
- fixed some MobiHeader size calculation bugs
- added facilities to edit the language fields
v0.10
- initial release

175
src/cli/WhisperPrep.java

@ -0,0 +1,175 @@
package cli;
import java.util.List;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import mobimeta.*;
public class WhisperPrep
{
private File inDir;
private File outDir;
private BufferedReader in;
public final static void main(String[] args)
{
if (args.length != 2) printUsage();
File inDir = new File(args[0]);
File outDir = new File(args[1]);
if (!inDir.exists() || !inDir.isDirectory())
printUsage("Input directory " + args[0] + " does not exist.");
if (!outDir.exists() || !outDir.isDirectory())
printUsage("Output directory " + args[1] + " does not exist.");
if (inDir.getAbsolutePath().equals(outDir.getAbsolutePath()))
printUsage("Input and output directories cannot be the same.");
WhisperPrep wp = new WhisperPrep(inDir, outDir);
wp.start();
}
private static void printUsage()
{
printUsage(null);
}
private static void printUsage(String message)
{
if (message != null) System.err.println(message);
System.err.println("Usage: WhisperPrep <input directory> <output directory>");
System.exit(0);
}
public WhisperPrep(File inDir, File outDir)
{
this.inDir = inDir;
this.outDir = outDir;
}
public void start()
{
LinkedList<File> fileList = new LinkedList<File>();
for (File f : inDir.listFiles())
{
if (f.isFile() && f.getName().toLowerCase().endsWith(".mobi"))
fileList.add(f);
}
in = new BufferedReader(new InputStreamReader(System.in));
int fileCount = fileList.size();
int current = 1;
for (File f : fileList)
{
log("********************");
log("File " + (current++) + "/" + fileCount);
log("Filename: " + f.getName());
MobiMeta meta = null;
try
{
meta = new MobiMeta(f);
}
catch (MobiMetaException e)
{
log("Error: " + e.getMessage());
continue;
}
log("Fullname: " + meta.getFullName());
List<EXTHRecord> exthList = meta.getEXTHRecords();
String encoding = meta.getCharacterEncoding();
String author = null;
String isbn = null;
String oldasin = null;
for (EXTHRecord rec : exthList)
{
switch (rec.getRecordType())
{
case 100:
author = StreamUtils.byteArrayToString(rec.getData(), encoding);
break;
case 104:
isbn = StreamUtils.byteArrayToString(rec.getData(), encoding);
break;
case 113:
oldasin = StreamUtils.byteArrayToString(rec.getData(), encoding);
break;
default:
break;
}
}
if (author != null) log("Author: " + author);
if (isbn != null) log("ISBN: " + isbn);
String asin = null;
if (oldasin == null)
asin = getInput("ASIN: ");
else
asin = getInput("ASIN [" + oldasin + "]: ");
if (asin.length() == 0)
{
if (oldasin != null)
asin = oldasin;
else
asin = null;
}
for (EXTHRecord rec : exthList)
{
int recType = rec.getRecordType();
if (recType == 113)
{
if (asin != null) exthList.remove(rec);
}
else if (recType == 501)
exthList.remove(rec);
}
if (asin != null) exthList.add(new EXTHRecord(113, asin, encoding));
exthList.add(new EXTHRecord(501, "EBOK", encoding));
meta.setEXTHRecords();
try
{
meta.saveToNewFile(new File(outDir, f.getName()));
}
catch (MobiMetaException e)
{
log("Error saving file: " + e.getMessage());
}
log("");
}
}
private void log(String message)
{
System.out.println(message);
}
private String getInput(String message)
{
System.out.print(message);
String value = null;
try
{
value = in.readLine();
}
catch (IOException e)
{
}
return (value == null)?"":value;
}
}

49
src/gui/CustomJTable.java

@ -0,0 +1,49 @@
package gui;
import javax.swing.JTable;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
public class CustomJTable extends JTable
{
/**
* This code was adapted from
* http://www.javalobby.org/java/forums/t84905.html
*
* A traditional JTable lets you specify custom renderers and editors on a
* column by column basis. This class lets you have different renderers and
* editors in the same column (but different rows).
*/
private static final long serialVersionUID = 1L;
public CustomJTable(TableModel model)
{
super(model);
}
public TableCellRenderer getCellRenderer(int row, int column)
{
TableColumn tableColumn = getColumnModel().getColumn(column);
TableCellRenderer renderer = tableColumn.getCellRenderer();
if (renderer != null) return renderer;
if (getValueAt(row, column) != null)
{
return getDefaultRenderer(getValueAt(row, column).getClass());
}
return getDefaultRenderer(getColumnClass(column));
}
public TableCellEditor getCellEditor(int row, int column)
{
TableColumn tableColumn = getColumnModel().getColumn(column);
TableCellEditor editor = tableColumn.getCellEditor();
if (editor != null) return editor;
if (getValueAt(row, column) != null)
{
return getDefaultEditor(getValueAt(row, column).getClass());
}
return getDefaultEditor(getColumnClass(column));
}
}

8
src/gui/EXTHAddRecordListener.java

@ -0,0 +1,8 @@
package gui;
import mobimeta.EXTHRecord;
public interface EXTHAddRecordListener
{
public void addEXTHRecord(EXTHRecord rec);
}

14
src/gui/GuiException.java

@ -0,0 +1,14 @@
package gui;
public class GuiException extends Exception
{
/**
*
*/
private static final long serialVersionUID = 1L;
public GuiException(String message)
{
super(message);
}
}

201
src/gui/GuiModel.java

@ -0,0 +1,201 @@
package gui;
import mobimeta.*;
import java.io.File;
import java.util.List;
import javax.swing.table.AbstractTableModel;
class GuiModel extends AbstractTableModel implements EXTHAddRecordListener, LanguageModel, MetaInfoProvider
{
/**
* This is essentially a proxy to the MobiMeta object. In addition, it
* serves as a TableModel for the JTable
*/
private static final long serialVersionUID = 1L;
private MobiMeta model = null;
// static globals for convenience
//
private static String fullName = null;
private static List<EXTHRecord> exthRecords = null;
private static String characterEncoding = null;
public GuiModel()
{
}
public void setModel(File f) throws GuiException
{
try
{
model = new MobiMeta(f);
}
catch (MobiMetaException e)
{
throw new GuiException(e.getMessage());
}
fullName = model.getFullName();
exthRecords = model.getEXTHRecords();
characterEncoding = model.getCharacterEncoding();
}
public String getColumnName(int col)
{
if (col == 0)
return "Record Type";
else
return "Value";
}
public int getColumnCount()
{
return 2;
}
public int getRowCount()
{
return (exthRecords == null)?0:exthRecords.size();
}
public Object getValueAt(int row, int col)
{
EXTHRecord rec = exthRecords.get(row);
int type = rec.getRecordType();
String typeDesc = rec.getTypeDescription();
if (col == 0)
{
if (typeDesc == null)
return Integer.toString(type);
else
return type + " (" + typeDesc + ")";
}
else
{
byte[] data = rec.getData();
if (typeDesc == null)
{
StringBuffer sb = new StringBuffer();
sb.append("{ ");
int len = data.length;
for (int i=0; i<len; i++)
{
if (i > 0) sb.append(", ");
sb.append(data[i] & 0xff);
}
sb.append(" }");
return sb.toString();
}
else if (EXTHRecord.isBooleanType(type))
{
int value = StreamUtils.byteArrayToInt(data);
if (value == 0)
return Boolean.FALSE;
else
return Boolean.TRUE;
}
else
return StreamUtils.byteArrayToString(data, characterEncoding);
}
}
public boolean isCellEditable(int row, int col)
{
if ((col == 0) || MobiCommon.safeMode) return false;
return exthRecords.get(row).isKnownType();
}
public void setValueAt(Object value, int row, int column)
{
if (column != 1) return;
if (value instanceof String)
exthRecords.get(row).setData((String)value, characterEncoding);
else if (value instanceof Boolean)
exthRecords.get(row).setData(((Boolean)value).booleanValue());
else
return;
fireTableCellUpdated(row, column);
}
public String getFullName()
{
return fullName;
}
public void setFullName(String s)
{
fullName = s;
}
public void removeRecordAtRow(int row)
{
exthRecords.remove(row);
fireTableRowsDeleted(row, row);
}
public void addEXTHRecord(EXTHRecord rec)
{
exthRecords.add(rec);
int lastIndex = exthRecords.size() - 1;
fireTableRowsInserted(lastIndex, lastIndex);
}
public static String getCharacterEncoding()
{
return characterEncoding;
}
public void save(File outputFile, boolean packHeader) throws GuiException
{
if (packHeader)
{
model.setFullName(fullName);
model.setEXTHRecords();
}
try
{
model.saveToNewFile(outputFile, packHeader);
}
catch (MobiMetaException e)
{
throw new GuiException("Problems encountered while saving file: "
+ e.getMessage());
}
}
public int getLocale()
{
return model.getLocale();
}
public int getDictInput()
{
return model.getDictInput();
}
public int getDictOutput()
{
return model.getDictOutput();
}
public void setLanguages(int locale, int dictInput, int dictOutput)
{
model.setLanguages(locale, dictInput, dictOutput);
}
public String getMetaInfo()
{
return model.getMetaInfo();
}
}

73
src/gui/HeaderInfoDialog.java

@ -0,0 +1,73 @@
package gui;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
public class HeaderInfoDialog extends JDialog implements ActionListener
{
/**
*
*/
private static final long serialVersionUID = 1L;
private final JPanel contentPanel = new JPanel();
private JButton closeButton;
private JScrollPane scrollPane;
private JTextArea textArea;
/**
* Create the dialog.
*/
public HeaderInfoDialog(JFrame parent, MetaInfoProvider infoProvider)
{
super(parent, "Header Info", true);
setBounds(100, 100, 450, 300);
getContentPane().setLayout(new BorderLayout());
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
getContentPane().add(contentPanel, BorderLayout.CENTER);
contentPanel.setLayout(new BorderLayout(0, 0));
{
scrollPane = new JScrollPane();
contentPanel.add(scrollPane, BorderLayout.CENTER);
{
textArea = new JTextArea(infoProvider.getMetaInfo());
textArea.setEditable(false);
scrollPane.setViewportView(textArea);
}
}
{
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
getContentPane().add(buttonPane, BorderLayout.SOUTH);
{
closeButton = new JButton("Close");
closeButton.addActionListener(this);
closeButton.setActionCommand("Cancel");
buttonPane.add(closeButton);
}
}
}
protected JButton getCloseButton() {
return closeButton;
}
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == closeButton)
{
setVisible(false);
dispose();
}
}
}

74
src/gui/LanguageCodes.java

@ -0,0 +1,74 @@
package gui;
public class LanguageCodes
{
// these codes are taken from:
// http://www.autoitscript.com/autoit3/docs/appendix/OSLangCodes.htm
//
public final static int[] code =
{ 0, 1078, 1052, 1025, 2049, 3073, 4097, 5121, 6145, 7169, 8193, 9217,
10241, 11265, 12289, 13313, 14337, 15361, 16385, 1067, 1068, 2092,
1069, 1059, 1026, 1027, 4, 1028, 2052, 3076, 4100, 5124, 1050, 1029,
1030, 1043, 2067, 9, 1033, 2057, 3081, 4105, 5129, 6153, 7177,
8201, 9225, 10249, 11273, 12297, 13321, 1061, 1080, 1073, 1035,
1036, 2060, 3084, 4108, 5132, 6156, 1079, 1031, 2055, 3079, 4103,
5127, 1032, 1037, 1081, 1038, 1039, 1057, 1040, 2064, 1041, 1087,
1111, 1042, 1062, 1063, 1071, 1086, 2110, 1102, 1044, 2068, 1045,
1046, 2070, 1048, 1049, 1103, 2074, 3098, 1051, 1060, 1034, 2058,
3082, 4106, 5130, 6154, 7178, 8202, 9226, 10250, 11274, 12298,
13322, 14346, 15370, 16394, 17418, 18442, 19466, 20490, 1089, 1053,
2077, 1097, 1092, 1054, 1055, 1058, 1056, 1091, 2115, 1066 };
public final static String[] description =
{ "Unspecified", "Afrikaans", "Albanian", "Arabic - Saudi Arabia",
"Arabic - Iraq", "Arabic - Egypt", "Arabic - Libya",
"Arabic - Algeria", "Arabic - Morocco", "Arabic - Tunisia",
"Arabic - Oman", "Arabic - Yemen", "Arabic - Syria",
"Arabic - Jordan", "Arabic - Lebanon", "Arabic - Kuwait",
"Arabic - UAE", "Arabic - Bahrain", "Arabic - Qatar", "Armenian",
"Azeri - Latin", "Azeri - Cyrillic", "Basque", "Belarusian",
"Bulgarian", "Catalan", "Chinese", "Chinese - Taiwan", "Chinese - PRC",
"Chinese - Hong Kong", "Chinese - Singapore", "Chinese - Macau",
"Croatian", "Czech", "Danish", "Dutch - Standard",
"Dutch - Belgian", "English", "English - United States",
"English Ð United Kingdom", "English - Australian",
"English - Canadian", "English - New Zealand", "English - Irish",
"English - South Africa", "English - Jamaica",
"English - Caribbean", "English - Belize", "English - Trinidad",
"English - Zimbabwe", "English - Philippines", "Estonian",
"Faeroese", "Farsi", "Finnish", "French - Standard",
"French - Belgian", "French - Canadian", "French - Swiss",
"French - Luxembourg", "French - Monaco", "Georgian",
"German - Standard", "German - Swiss", "German - Austrian",
"German - Luxembourg", "German - Liechtenstei", "Greek", "Hebrew",
"Hindi", "Hungarian", "Icelandic", "Indonesian",
"Italian - Standard", "Italian - Swiss", "Japanese", "Kazakh",
"Konkani", "Korean", "Latvian", "Lithuanian", "Macedonian",
"Malay - Malaysia", "Malay - Brunei Darussalam", "Marathi",
"Norwegian - Bokmal", "Norwegian - Nynorsk", "Polish",
"Portuguese - Brazilian", "Portuguese - Standard", "Romanian",
"Russian", "Sanskrit", "Serbian - Latin", "Serbian - Cyrillic",
"Slovak", "Slovenian", "Spanish - Traditional Sort",
"Spanish - Mexican", "Spanish - Modern Sort",
"Spanish - Guatemala", "Spanish - Costa Rica", "Spanish - Panama",
"Spanish - Dominican Republic", "Spanish - Venezuela",
"Spanish - Colombia", "Spanish - Peru", "Spanish - Argentina",
"Spanish - Ecuador", "Spanish - Chile", "Spanish - Uruguay",
"Spanish - Paraguay", "Spanish - Bolivia", "Spanish - El Salvador",
"Spanish - Honduras", "Spanish - Nicaragua",
"Spanish - Puerto Rico", "Swahili", "Swedish", "Swedish - Finland",
"Tamil", "Tatar", "Thai", "Turkish", "Ukrainian", "Urdu",
"Uzbek - Latin", "Uzbek - Cyrillic", "Vietnamese" };
public static int codeToIndex(int key)
{
int len = code.length;
for (int i = 0; i < len; i++)
{
if (code[i] == key) return i;
}
return -1;
}
}

258
src/gui/LanguageDialog.java

@ -0,0 +1,258 @@
package gui;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.GridBagLayout;
import java.awt.Component;
import javax.swing.Box;
import javax.swing.JLabel;
import java.awt.GridBagConstraints;
import java.awt.Font;
import javax.swing.SwingConstants;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComboBox;
public class LanguageDialog extends JDialog implements ActionListener
{
/**
*
*/
private static final long serialVersionUID = 1L;
private final JPanel contentPanel = new JPanel();
private JButton okButton;
private JButton cancelButton;
private JComboBox cbLanguage;
private JComboBox cbDictInput;
private JComboBox cbDictOutput;
// store original values here
private int locale;
private int dictInput;
private int dictOutput;
// these values indicate if they are known values
private boolean localeExists;
private boolean dictInputExists;
private boolean dictOutputExists;
private LanguageModel model;
/**
* Create the dialog.
*/
public LanguageDialog(JFrame parent, LanguageModel model)
{
super(parent, true);
this.model = model;
locale = model.getLocale();
dictInput = model.getDictInput();
dictOutput = model.getDictOutput();
localeExists = (LanguageCodes.codeToIndex(locale) != -1);
dictInputExists = (LanguageCodes.codeToIndex(dictInput) != -1);
dictOutputExists = (LanguageCodes.codeToIndex(dictOutput) != -1);
String[] localeChoices;
String[] dictInputChoices;
String[] dictOutputChoices;
// assemble choices for combo boxes
//
if (localeExists)
localeChoices = LanguageCodes.description;
else
{
localeChoices = new String[LanguageCodes.description.length + 1];
System.arraycopy(LanguageCodes.description, 0, localeChoices, 0,
LanguageCodes.description.length);
localeChoices[localeChoices.length - 1] = "Unknown (" + locale
+ ")";
}
if (dictInputExists)
dictInputChoices = LanguageCodes.description;
else
{
dictInputChoices = new String[LanguageCodes.description.length + 1];
System.arraycopy(LanguageCodes.description, 0, dictInputChoices, 0,
LanguageCodes.description.length);
dictInputChoices[dictInputChoices.length - 1] = "Unknown ("
+ dictInput + ")";
}
if (dictOutputExists)
dictOutputChoices = LanguageCodes.description;
else
{
dictOutputChoices = new String[LanguageCodes.description.length + 1];
System.arraycopy(LanguageCodes.description, 0, dictOutputChoices,
0, LanguageCodes.description.length);
dictOutputChoices[dictOutputChoices.length - 1] = "Unknown ("
+ dictOutput + ")";
}
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setBounds(100, 100, 450, 300);
getContentPane().setLayout(new BorderLayout());
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
getContentPane().add(contentPanel, BorderLayout.CENTER);
contentPanel.setLayout(new BorderLayout(0, 0));
{
Component verticalStrut = Box.createVerticalStrut(20);
contentPanel.add(verticalStrut, BorderLayout.NORTH);
}
{
Component verticalStrut = Box.createVerticalStrut(20);
contentPanel.add(verticalStrut, BorderLayout.SOUTH);
}
{
Component horizontalStrut = Box.createHorizontalStrut(20);
contentPanel.add(horizontalStrut, BorderLayout.WEST);
}
{
Component horizontalStrut = Box.createHorizontalStrut(20);
contentPanel.add(horizontalStrut, BorderLayout.EAST);
}
{
JPanel panel = new JPanel();
contentPanel.add(panel, BorderLayout.CENTER);
GridBagLayout gbl_panel = new GridBagLayout();
gbl_panel.columnWidths = new int[]
{ 0, 0, 0 };
gbl_panel.rowHeights = new int[]
{ 0, 0, 0, 0 };
gbl_panel.columnWeights = new double[]
{ 0.0, 1.0, Double.MIN_VALUE };
gbl_panel.rowWeights = new double[]
{ 0.0, 0.0, 0.0, Double.MIN_VALUE };
panel.setLayout(gbl_panel);
{
JLabel lblLanguage = new JLabel("Language");
lblLanguage.setHorizontalAlignment(SwingConstants.RIGHT);
lblLanguage.setFont(new Font("Lucida Grande", Font.BOLD, 13));
GridBagConstraints gbc_lblLanguage = new GridBagConstraints();
gbc_lblLanguage.anchor = GridBagConstraints.EAST;
gbc_lblLanguage.insets = new Insets(0, 0, 5, 5);
gbc_lblLanguage.gridx = 0;
gbc_lblLanguage.gridy = 0;
panel.add(lblLanguage, gbc_lblLanguage);
}
{
cbLanguage = new JComboBox(localeChoices);
cbLanguage.setSelectedIndex(localeExists ? LanguageCodes
.codeToIndex(locale) : (localeChoices.length - 1));
GridBagConstraints gbc_cbLanguage = new GridBagConstraints();
gbc_cbLanguage.insets = new Insets(0, 0, 5, 0);
gbc_cbLanguage.fill = GridBagConstraints.HORIZONTAL;
gbc_cbLanguage.gridx = 1;
gbc_cbLanguage.gridy = 0;
panel.add(cbLanguage, gbc_cbLanguage);
}
{
JLabel lblDictionaryInput = new JLabel("Dictionary Input");
lblDictionaryInput.setHorizontalAlignment(SwingConstants.RIGHT);
lblDictionaryInput.setFont(new Font("Lucida Grande", Font.BOLD,
13));
GridBagConstraints gbc_lblDictionaryInput = new GridBagConstraints();
gbc_lblDictionaryInput.anchor = GridBagConstraints.EAST;
gbc_lblDictionaryInput.insets = new Insets(0, 0, 5, 5);
gbc_lblDictionaryInput.gridx = 0;
gbc_lblDictionaryInput.gridy = 1;
panel.add(lblDictionaryInput, gbc_lblDictionaryInput);
}
{
cbDictInput = new JComboBox(dictInputChoices);
cbDictInput
.setSelectedIndex(dictInputExists ? LanguageCodes
.codeToIndex(dictInput)
: (dictInputChoices.length - 1));
GridBagConstraints gbc_cbDictInput = new GridBagConstraints();
gbc_cbDictInput.insets = new Insets(0, 0, 5, 0);
gbc_cbDictInput.fill = GridBagConstraints.HORIZONTAL;
gbc_cbDictInput.gridx = 1;
gbc_cbDictInput.gridy = 1;
panel.add(cbDictInput, gbc_cbDictInput);
}
{
JLabel lblDictionaryOutput = new JLabel("Dictionary Output");
lblDictionaryOutput
.setHorizontalAlignment(SwingConstants.RIGHT);
lblDictionaryOutput.setFont(new Font("Lucida Grande",
Font.BOLD, 13));
GridBagConstraints gbc_lblDictionaryOutput = new GridBagConstraints();
gbc_lblDictionaryOutput.anchor = GridBagConstraints.EAST;
gbc_lblDictionaryOutput.insets = new Insets(0, 0, 0, 5);
gbc_lblDictionaryOutput.gridx = 0;
gbc_lblDictionaryOutput.gridy = 2;
panel.add(lblDictionaryOutput, gbc_lblDictionaryOutput);
}
{
cbDictOutput = new JComboBox(dictOutputChoices);
cbDictOutput.setSelectedIndex(dictOutputExists ? LanguageCodes
.codeToIndex(dictOutput)
: (dictOutputChoices.length - 1));
GridBagConstraints gbc_cbDictOutput = new GridBagConstraints();
gbc_cbDictOutput.fill = GridBagConstraints.HORIZONTAL;
gbc_cbDictOutput.gridx = 1;
gbc_cbDictOutput.gridy = 2;
panel.add(cbDictOutput, gbc_cbDictOutput);
}
}
{
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
getContentPane().add(buttonPane, BorderLayout.SOUTH);
{
okButton = new JButton("OK");
okButton.addActionListener(this);
buttonPane.add(okButton);
getRootPane().setDefaultButton(okButton);
}
{
cancelButton = new JButton("Cancel");
cancelButton.addActionListener(this);
buttonPane.add(cancelButton);
}
}
}
public void actionPerformed(ActionEvent event)
{
Object source = event.getSource();
if (source == okButton)
{
int localeIndex = cbLanguage.getSelectedIndex();
if ((localeIndex >= 0) && (localeIndex < LanguageCodes.code.length))
locale = LanguageCodes.code[localeIndex];
int dictInputIndex = cbDictInput.getSelectedIndex();
if ((dictInputIndex >= 0) && (dictInputIndex < LanguageCodes.code.length))
dictInput = LanguageCodes.code[dictInputIndex];
int dictOutputIndex = cbDictOutput.getSelectedIndex();
if ((dictOutputIndex >= 0) && (dictOutputIndex < LanguageCodes.code.length))
dictOutput = LanguageCodes.code[dictOutputIndex];
model.setLanguages(locale, dictInput, dictOutput);
setVisible(false);
dispose();
}
else if (source == cancelButton)
{
setVisible(false);
dispose();
}
}
}

12
src/gui/LanguageModel.java

@ -0,0 +1,12 @@
package gui;
public interface LanguageModel
{
public int getLocale();
public int getDictInput();
public int getDictOutput();
public void setLanguages(int locale, int dictInput, int dictOutput);
}

667
src/gui/Main.java

@ -0,0 +1,667 @@
package gui;
import java.io.*;
import java.util.HashSet;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import java.awt.GridBagLayout;
import javax.swing.JLabel;
import java.awt.FileDialog;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.Rectangle;
import javax.swing.JSeparator;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.Box;
import javax.swing.SwingConstants;
import java.awt.Font;
import javax.swing.JTextField;
import java.awt.Color;
import javax.swing.JScrollPane;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JTable;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JTextArea;
import mobimeta.MobiCommon;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
public class Main implements ListSelectionListener, ActionListener,
TableModelListener, LanguageModel
{
private FileDialog openFileChooser = null;
private FileDialog saveFileChooser = null;
private JFrame frame;
private JTextArea lblInputFilename;
private JTextArea lblOutputFilename;
private JTextField tfFullName;
private JTable table;
private JButton buttonRemove;
private JButton buttonAdd;
private JButton buttonSave;
private JButton btnLanguage;
private JButton btnHeaderInfo;
private GuiModel model;
private File outputFile;
private boolean packHeader = false;
private JMenuItem mntmOpen;
private JMenuItem mntmSave;
/**
* Launch the application.
*/
public static void main(String[] args)
{
System.setProperty("apple.laf.useScreenMenuBar", "true");
System.setProperty("com.apple.mrj.application.apple.menu.about.name",
"Mobi Meta Editor");
HashSet<String> optionsSet = new HashSet<String>();
File inputFile = null;
for (int i=0; i<args.length; i++)
{
String arg = args[i];
if (arg.startsWith("-"))
optionsSet.add(arg);
else if (inputFile == null)
inputFile = new File(arg);
else
printUsage();
}
if ((inputFile != null) && (!inputFile.exists() || !inputFile.isFile()))
{
System.err.println("Input file " + inputFile.getAbsolutePath()
+ " does not exist or is not a file.");
System.exit(1);
}
if (optionsSet.contains("-h") || optionsSet.contains("--help"))
printUsage();
if (optionsSet.contains("-s"))
MobiCommon.safeMode = true;
if (optionsSet.contains("-d"))
MobiCommon.debug = true;
// get around inner class mumbo-jumbo
//
final File f = inputFile;
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
Main window = new Main(f);
window.frame.setVisible(true);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
private static void printUsage()
{
System.err.println("Usage: mobimeta [switches] [input file]");
System.err.println(" -h\tthis message");
System.err
.println(" -s\tsafe mode - does not change the size of the mobi header record");
System.err.println(" -d\tdebug mode");
System.exit(0);
}
/**
* Create the application.
*/
public Main(File inputFile)
{
model = new GuiModel();
model.addTableModelListener(this);
initialize();
if (inputFile == null)
inputFile = getSourceFile();
if (inputFile == null)
{
showAlert("No mobi file selected.");
System.exit(0);
}
}
public void valueChanged(ListSelectionEvent e)
{
if (!MobiCommon.safeMode)
buttonRemove.setEnabled(table.getSelectedRow() != -1);
}
public void pickSaveTarget()
{
if (saveFileChooser == null)
{
saveFileChooser = new FileDialog(frame, "Select mobi file", FileDialog.SAVE);
saveFileChooser.setFilenameFilter(new MobiFileFilter());
}
if (outputFile != null)
{
saveFileChooser.setDirectory(outputFile.getParent());
saveFileChooser.setFile(outputFile.getName());
}
saveFileChooser.setVisible(true);
if (saveFileChooser.getFile() != null)
{
outputFile = new File(saveFileChooser.getDirectory(), saveFileChooser.getFile());
lblOutputFilename.setText(outputFile.getAbsolutePath());
}
}
public void actionPerformed(ActionEvent event)
{
Object source = event.getSource();
if (source == buttonAdd)
{
NewRecordDialog dialog = new NewRecordDialog(frame, model);
dialog.setVisible(true);
}
else if (source == buttonRemove)
{
int row = table.getSelectedRow();
if (row == -1) return;
model.removeRecordAtRow(row);
}
else if ((source == buttonSave) || (source == mntmSave))
{
if (packHeader) model.setFullName(tfFullName.getText());
try
{
File tmpOutput = null;
if (lblInputFilename.getText().equals(lblOutputFilename.getText()))
tmpOutput = File.createTempFile("mobimeta", ".mobi");
else
tmpOutput = outputFile;
model.save(tmpOutput, packHeader);
if (!tmpOutput.equals(outputFile))
{
if (!tmpOutput.renameTo(outputFile))
{
showAlert("Error renaming temp file to " + outputFile.getAbsolutePath());
return;
}
}
setWindowChangedStatus(false);
showAlert("File saved.");
}
catch (GuiException e)
{
showAlert(e.getMessage());
}
catch (IOException e)
{
showAlert("Could not create temp file for writing: " + e.getMessage());
}
}
else if (source == btnLanguage)
{
LanguageDialog dialog = new LanguageDialog(frame, this);
dialog.setVisible(true);
}
else if (source == btnHeaderInfo)
{
HeaderInfoDialog dialog = new HeaderInfoDialog(frame, model);
dialog.setVisible(true);
}
else if (source == mntmOpen)
{
getSourceFile();
}
}
/**
* Initialize the contents of the frame.
*/
private void initialize()
{
frame = new JFrame();
frame.setTitle("Mobi Meta Editor");
frame.setBounds(50, 50, 800, 700);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout(0, 0));
Component horizontalStrut = Box.createHorizontalStrut(20);
frame.getContentPane().add(horizontalStrut, BorderLayout.WEST);
Component horizontalStrut_1 = Box.createHorizontalStrut(20);
frame.getContentPane().add(horizontalStrut_1, BorderLayout.EAST);
Component verticalStrut = Box.createVerticalStrut(20);
frame.getContentPane().add(verticalStrut, BorderLayout.NORTH);
Component verticalStrut_1 = Box.createVerticalStrut(20);
frame.getContentPane().add(verticalStrut_1, BorderLayout.SOUTH);
JPanel panel = new JPanel();
frame.getContentPane().add(panel, BorderLayout.CENTER);
GridBagLayout gbl_panel = new GridBagLayout();
gbl_panel.columnWidths = new int[]
{ 0, 0, 0, 0, 0 };
gbl_panel.rowHeights = new int[]
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
gbl_panel.columnWeights = new double[]
{ 1.0, 0.0, 1.0, 0.0, Double.MIN_VALUE };
gbl_panel.rowWeights = new double[]
{ 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, Double.MIN_VALUE };
panel.setLayout(gbl_panel);
JLabel lblInput = new JLabel("Input Filename");
lblInput.setFont(new Font("Lucida Grande", Font.BOLD, 13));
lblInput.setVerticalAlignment(SwingConstants.TOP);
GridBagConstraints gbc_lblInput = new GridBagConstraints();
gbc_lblInput.anchor = GridBagConstraints.NORTHEAST;
gbc_lblInput.insets = new Insets(0, 0, 5, 5);
gbc_lblInput.gridx = 0;
gbc_lblInput.gridy = 0;
panel.add(lblInput, gbc_lblInput);
Component horizontalStrut_2 = Box.createHorizontalStrut(20);
GridBagConstraints gbc_horizontalStrut_2 = new GridBagConstraints();
gbc_horizontalStrut_2.insets = new Insets(0, 0, 5, 5);
gbc_horizontalStrut_2.gridx = 1;
gbc_horizontalStrut_2.gridy = 0;
panel.add(horizontalStrut_2, gbc_horizontalStrut_2);
lblInputFilename = new JTextArea(0, 40);
lblInputFilename.setEditable(false);
lblInputFilename.setLineWrap(true);
GridBagConstraints gbc_lblInputFilename = new GridBagConstraints();
gbc_lblInputFilename.fill = GridBagConstraints.BOTH;
gbc_lblInputFilename.anchor = GridBagConstraints.NORTHWEST;
gbc_lblInputFilename.insets = new Insets(0, 0, 5, 5);
gbc_lblInputFilename.gridx = 2;
gbc_lblInputFilename.gridy = 0;
panel.add(lblInputFilename, gbc_lblInputFilename);
JLabel lblOutput = new JLabel("Output Filename");
lblOutput.setFont(new Font("Lucida Grande", Font.BOLD, 13));
lblOutput.setVerticalAlignment(SwingConstants.TOP);
lblOutput.setHorizontalAlignment(SwingConstants.RIGHT);
GridBagConstraints gbc_lblOutput = new GridBagConstraints();
gbc_lblOutput.anchor = GridBagConstraints.NORTHEAST;
gbc_lblOutput.insets = new Insets(0, 0, 5, 5);
gbc_lblOutput.gridx = 0;
gbc_lblOutput.gridy = 1;
panel.add(lblOutput, gbc_lblOutput);
lblOutputFilename = new JTextArea(0, 40);
lblOutputFilename.setEditable(false);
lblOutputFilename.setLineWrap(true);
lblOutputFilename.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
pickSaveTarget();
}
});
GridBagConstraints gbc_lblOutputFilename = new GridBagConstraints();
gbc_lblOutputFilename.fill = GridBagConstraints.BOTH;
gbc_lblOutputFilename.anchor = GridBagConstraints.NORTHWEST;
gbc_lblOutputFilename.insets = new Insets(0, 0, 5, 5);
gbc_lblOutputFilename.gridx = 2;
gbc_lblOutputFilename.gridy = 1;
panel.add(lblOutputFilename, gbc_lblOutputFilename);
JSeparator separator = new JSeparator();
separator.setForeground(Color.DARK_GRAY);
GridBagConstraints gbc_separator = new GridBagConstraints();
gbc_separator.fill = GridBagConstraints.BOTH;
gbc_separator.gridwidth = 3;
gbc_separator.insets = new Insets(0, 0, 5, 5);
gbc_separator.gridx = 0;
gbc_separator.gridy = 2;
panel.add(separator, gbc_separator);
Component separatorStrut1 = Box.createVerticalStrut(10);
GridBagConstraints gbc_strut1 = new GridBagConstraints();
gbc_strut1.fill = GridBagConstraints.VERTICAL;
gbc_strut1.gridwidth = 1;
gbc_strut1.insets = new Insets(0, 0, 5, 0);
gbc_strut1.gridx = 3;
gbc_strut1.gridy = 2;
panel.add(separatorStrut1, gbc_strut1);
JLabel lblTitle = new JLabel("Full Name");
lblTitle.setFont(new Font("Lucida Grande", Font.BOLD, 13));
GridBagConstraints gbc_lblTitle = new GridBagConstraints();
gbc_lblTitle.anchor = GridBagConstraints.NORTHEAST;
gbc_lblTitle.insets = new Insets(0, 0, 5, 5);
gbc_lblTitle.gridx = 0;
gbc_lblTitle.gridy = 3;
panel.add(lblTitle, gbc_lblTitle);
tfFullName = new JTextField(15);
if (MobiCommon.safeMode)
tfFullName.setEditable(false);
else
{
tfFullName.getDocument().addDocumentListener(new DocumentListener()
{
public void changedUpdate(DocumentEvent arg0)
{
packHeader = true;
setWindowChangedStatus(true);
}
public void insertUpdate(DocumentEvent arg0)
{
packHeader = true;
setWindowChangedStatus(true);
}
public void removeUpdate(DocumentEvent arg0)
{
packHeader = true;
setWindowChangedStatus(true);
}
});
}
GridBagConstraints gbc_tfFullName = new GridBagConstraints();
gbc_tfFullName.fill = GridBagConstraints.HORIZONTAL;
gbc_tfFullName.anchor = GridBagConstraints.NORTHWEST;
gbc_tfFullName.insets = new Insets(0, 0, 5, 5);
gbc_tfFullName.gridx = 2;
gbc_tfFullName.gridy = 3;
panel.add(tfFullName, gbc_tfFullName);
JPanel panel_3 = new JPanel();
GridBagConstraints gbc_panel_3 = new GridBagConstraints();
gbc_panel_3.insets = new Insets(0, 0, 5, 5);
gbc_panel_3.fill = GridBagConstraints.BOTH;
gbc_panel_3.gridx = 2;
gbc_panel_3.gridy = 4;
panel.add(panel_3, gbc_panel_3);
btnLanguage = new JButton("Language...");
btnLanguage.addActionListener(this);
btnHeaderInfo = new JButton("Header Info...");
btnHeaderInfo.addActionListener(this);
panel_3.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));
panel_3.add(btnLanguage);
panel_3.add(btnHeaderInfo);
JLabel lblExthRecords = new JLabel("EXTH Records");
lblExthRecords.setFont(new Font("Lucida Grande", Font.BOLD, 13));
GridBagConstraints gbc_lblExthRecords = new GridBagConstraints();
gbc_lblExthRecords.anchor = GridBagConstraints.NORTHEAST;
gbc_lblExthRecords.insets = new Insets(0, 0, 5, 5);
gbc_lblExthRecords.gridx = 0;
gbc_lblExthRecords.gridy = 5;
panel.add(lblExthRecords, gbc_lblExthRecords);
JScrollPane scrollPane = new JScrollPane();
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.insets = new Insets(0, 0, 5, 5);
gbc_scrollPane.gridx = 2;
gbc_scrollPane.gridy = 5;
panel.add(scrollPane, gbc_scrollPane);
table = new CustomJTable(model);
table.getColumnModel().getColumn(0).setPreferredWidth(100);
table.getSelectionModel().addListSelectionListener(this);
scrollPane.setViewportView(table);
JPanel panel_1 = new JPanel();
GridBagConstraints gbc_panel_1 = new GridBagConstraints();
gbc_panel_1.insets = new Insets(0, 0, 5, 5);
gbc_panel_1.anchor = GridBagConstraints.EAST;
gbc_panel_1.gridx = 2;
gbc_panel_1.gridy = 6;
panel.add(panel_1, gbc_panel_1);
panel_1.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
buttonAdd = new JButton("+");
buttonAdd.addActionListener(this);
panel_1.add(buttonAdd);
buttonRemove = new JButton("-");
buttonRemove.addActionListener(this);
buttonRemove.setEnabled(false);
panel_1.add(buttonRemove);
if (MobiCommon.safeMode)
{
buttonAdd.setEnabled(false);
buttonRemove.setEnabled(false);
}
JSeparator separator_1 = new JSeparator();
separator_1.setForeground(Color.DARK_GRAY);
GridBagConstraints gbc_separator_1 = new GridBagConstraints();
gbc_separator_1.insets = new Insets(0, 0, 5, 5);
gbc_separator_1.fill = GridBagConstraints.HORIZONTAL;
gbc_separator_1.gridwidth = 3;
gbc_separator_1.gridx = 0;
gbc_separator_1.gridy = 7;
panel.add(separator_1, gbc_separator_1);
Component separatorStrut2 = Box.createVerticalStrut(10);
GridBagConstraints gbc_strut2 = new GridBagConstraints();
gbc_strut2.fill = GridBagConstraints.VERTICAL;
gbc_strut2.gridwidth = 1;
gbc_strut2.insets = new Insets(0, 0, 5, 0);
gbc_strut2.gridx = 3;
gbc_strut2.gridy = 7;
panel.add(separatorStrut2, gbc_strut2);
JPanel panel_2 = new JPanel();
GridBagConstraints gbc_panel_2 = new GridBagConstraints();
gbc_panel_2.insets = new Insets(0, 0, 0, 5);
gbc_panel_2.anchor = GridBagConstraints.WEST;
gbc_panel_2.gridwidth = 3;
gbc_panel_2.fill = GridBagConstraints.VERTICAL;
gbc_panel_2.gridx = 0;
gbc_panel_2.gridy = 8;
panel.add(panel_2, gbc_panel_2);
buttonSave = new JButton("Save");
buttonSave.addActionListener(this);
panel_2.add(buttonSave);
JMenuBar menuBar = new JMenuBar();
frame.setJMenuBar(menuBar);
JMenu mnFile = new JMenu("File");
menuBar.add(mnFile);
mntmOpen = new JMenuItem("Open...");
mntmOpen.addActionListener(this);
mnFile.add(mntmOpen);
mntmSave = new JMenuItem("Save");
mntmSave.addActionListener(this);
mnFile.add(mntmSave);
}
private File getSourceFile()
{
if (openFileChooser == null)
{
openFileChooser = new FileDialog(frame, "Select mobi file", FileDialog.LOAD);
openFileChooser.setFilenameFilter(new MobiFileFilter());
}
openFileChooser.setVisible(true);
File source = null;
String dir = openFileChooser.getDirectory();
String file = openFileChooser.getFile();
if ((dir != null) && (file != null))
source = new File(dir, file);
if (source != null)
{
try
{
model.setModel(source);
}
catch (GuiException e)
{
showAlert("Could not parse mobi file: " + e.getMessage());
System.exit(0);
}
lblInputFilename.setText(source.getAbsolutePath());
tfFullName.setText(model.getFullName());
outputFile = getOutputFile(source);
lblOutputFilename.setText(outputFile.getAbsolutePath());
// we trigger the window modified indicator when we set tfFullName
//
setWindowChangedStatus(false);
packHeader = false;
}
return source;
}
private File getOutputFile(File inputFile)
{
File parent = inputFile.getParentFile();
String inputName = inputFile.getName();
int dot = inputName.lastIndexOf('.');
String outputName;
if (dot == -1)
outputName = inputName + "_new";
else
outputName = inputName.substring(0, dot) + "_new"
+ inputName.substring(dot);
return new File(parent, outputName);
}
private void showAlert(String message)
{
JOptionPane.showMessageDialog(frame, message);
}
// scroll to the newly added/deleted row
//
public void tableChanged(TableModelEvent event)
{
int eventType = event.getType();
int row = event.getLastRow();
if (eventType == TableModelEvent.INSERT)
{
table.getSelectionModel().setSelectionInterval(row, row);
table.scrollRectToVisible(new Rectangle(table.getCellRect(row, 0,
true)));
packHeader = true;
setWindowChangedStatus(true);
}
else if (eventType == TableModelEvent.DELETE)
{
boolean select = false;
int numRows = model.getRowCount();
if (numRows > row)
{
select = true;
}
else if (numRows > 0)
{
select = true;
row = numRows - 1;
}
if (select)
{
table.getSelectionModel().setSelectionInterval(row, row);
table.scrollRectToVisible(new Rectangle(table.getCellRect(row,
0, true)));
}
packHeader = true;
setWindowChangedStatus(true);
}
else if (eventType == TableModelEvent.UPDATE)
{
packHeader = true;
setWindowChangedStatus(true);
}
}
protected void setWindowChangedStatus(boolean status)
{
frame.getRootPane().putClientProperty("Window.documentModified",
Boolean.valueOf(status));
}
// we implement the LanguageModel interface because we want to intercept the
// setLanguages() call so that we can set the window status changed flag
//
public int getLocale()
{
return model.getLocale();
}
// we implement the LanguageModel interface because we want to intercept the
// setLanguages() call so that we can set the window status changed flag
//
public int getDictInput()
{
return model.getDictInput();
}
// we implement the LanguageModel interface because we want to intercept the
// setLanguages() call so that we can set the window status changed flag
//
public int getDictOutput()
{
return model.getDictOutput();
}
// we implement the LanguageModel interface because we want to intercept the
// setLanguages() call so that we can set the window status changed flag
//
public void setLanguages(int locale, int dictInput, int dictOutput)
{
model.setLanguages(locale, dictInput, dictOutput);
setWindowChangedStatus(true);
}
protected JMenuItem getMntmOpen() {
return mntmOpen;
}
protected JMenuItem getMntmSave() {
return mntmSave;
}
}

6
src/gui/MetaInfoProvider.java

@ -0,0 +1,6 @@
package gui;
public interface MetaInfoProvider
{
public String getMetaInfo();
}

32
src/gui/MobiFileFilter.java

@ -0,0 +1,32 @@
package gui;
import java.io.File;
import java.io.FilenameFilter;
import javax.swing.filechooser.FileFilter;
class MobiFileFilter extends FileFilter implements FilenameFilter
{
// to make it work with JFileChooser
//
public boolean accept(File f)
{
if (f.isDirectory()) return true;
return (f.getName().toLowerCase().endsWith(".azw") || f.getName().toLowerCase().endsWith(".mobi"));
}
public String getDescription()
{
return "*.azw,*.mobi";
}
// to make it work with java.awt.FileDialog
//
public boolean accept(File f, String name)
{
return (name.toLowerCase().endsWith(".azw") || name.toLowerCase().endsWith(".mobi"));
}
}

175
src/gui/NewRecordDialog.java

@ -0,0 +1,175 @@
package gui;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JLabel;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import javax.swing.JComboBox;
import java.awt.Insets;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JTextField;
import mobimeta.EXTHRecord;
public class NewRecordDialog extends JDialog implements ActionListener
{
/**
*
*/
private static final long serialVersionUID = 1L;
private final JPanel contentPanel = new JPanel();
private JTextField tfValue;
private JComboBox typeCombo;
private EXTHAddRecordListener listener;
private JButton addButton;
private JButton cancelButton;
/**
* Create the dialog.
*/
public NewRecordDialog(JFrame parent, EXTHAddRecordListener listener)
{
super(parent, true);
this.listener = listener;
String[] comboValues = new String[EXTHRecord.knownTypes.length];
for (int i=0; i<comboValues.length; i++)
{
comboValues[i] = EXTHRecord.knownTypes[i] + " (" + EXTHRecord.knownDesc[i] + ")";
}
setBounds(100, 100, 450, 300);
getContentPane().setLayout(new BorderLayout());
contentPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
getContentPane().add(contentPanel, BorderLayout.CENTER);
GridBagLayout gbl_contentPanel = new GridBagLayout();
gbl_contentPanel.columnWidths = new int[]{0, 0};
gbl_contentPanel.rowHeights = new int[]{0, 0, 0};
gbl_contentPanel.columnWeights = new double[]{0.0, 1.0};
gbl_contentPanel.rowWeights = new double[]{0.0, 0.0, Double.MIN_VALUE};
contentPanel.setLayout(gbl_contentPanel);
{
JLabel lblType = new JLabel("Type");
lblType.setFont(new Font("Lucida Grande", Font.BOLD, 13));
GridBagConstraints gbc_lblType = new GridBagConstraints();
gbc_lblType.anchor = GridBagConstraints.EAST;
gbc_lblType.insets = new Insets(0, 0, 5, 5);
gbc_lblType.gridx = 0;
gbc_lblType.gridy = 0;
contentPanel.add(lblType, gbc_lblType);
}
{
typeCombo = new JComboBox(comboValues);
GridBagConstraints gbc_typeCombo = new GridBagConstraints();
gbc_typeCombo.insets = new Insets(0, 0, 5, 0);
gbc_typeCombo.fill = GridBagConstraints.HORIZONTAL;
gbc_typeCombo.gridx = 1;
gbc_typeCombo.gridy = 0;
contentPanel.add(typeCombo, gbc_typeCombo);
}
{
JLabel lblValue = new JLabel("Value");
lblValue.setFont(new Font("Lucida Grande", Font.BOLD, 13));
GridBagConstraints gbc_lblValue = new GridBagConstraints();
gbc_lblValue.anchor = GridBagConstraints.EAST;
gbc_lblValue.insets = new Insets(0, 0, 0, 5);
gbc_lblValue.gridx = 0;
gbc_lblValue.gridy = 1;
contentPanel.add(lblValue, gbc_lblValue);
}
{
tfValue = new JTextField();
GridBagConstraints gbc_tfValue = new GridBagConstraints();
gbc_tfValue.fill = GridBagConstraints.HORIZONTAL;
gbc_tfValue.gridx = 1;
gbc_tfValue.gridy = 1;
contentPanel.add(tfValue, gbc_tfValue);
tfValue.setColumns(20);
}
{
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
getContentPane().add(buttonPane, BorderLayout.SOUTH);
{
addButton = new JButton("Add");
addButton.addActionListener(this);
buttonPane.add(addButton);
getRootPane().setDefaultButton(addButton);
}
{
cancelButton = new JButton("Cancel");
cancelButton.addActionListener(this);
buttonPane.add(cancelButton);
}
}
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
protected JComboBox getTypeCombo() {
return typeCombo;
}
public void actionPerformed(ActionEvent event)
{
Object source = event.getSource();
if (source == addButton)
{
String value = tfValue.getText();
if (value.length() == 0) return;
int typeIndex = typeCombo.getSelectedIndex();
if (typeIndex == -1) return;
int type = EXTHRecord.knownTypes[typeIndex];
EXTHRecord rec = null;
if (EXTHRecord.isBooleanType(type)) // this is an ugly hack - we really should present a checkbox to the user
{
boolean boolValue = false;
if (value.equals("1")
||
value.toLowerCase().equals("true")
||
value.toLowerCase().equals("on")
||
value.toLowerCase().equals("yes"))
{
boolValue = true;
}
rec = new EXTHRecord(type, boolValue);
}
else
rec = new EXTHRecord(type, value, GuiModel.getCharacterEncoding());
listener.addEXTHRecord(rec);
setVisible(false);
dispose();
}
else if (source == cancelButton)
{
setVisible(false);
dispose();
}
}
protected JButton getAddButton() {
return addButton;
}
protected JButton getCancelButton() {
return cancelButton;
}
protected JTextField getTfValue() {
return tfValue;
}
}

185
src/mobimeta/EXTHHeader.java

@ -0,0 +1,185 @@
package mobimeta;
import java.io.*;
import java.util.*;
public class EXTHHeader
{
private byte[] identifier = { 69, 88, 84, 72 };
private byte[] headerLength = { 0, 0, 0, 0 };
private byte[] recordCount = { 0, 0, 0, 0 };
private List<EXTHRecord> recordList = null;
public EXTHHeader()
{
recordList = new LinkedList<EXTHRecord>();
}
public EXTHHeader(List<EXTHRecord> list)
{
setRecordList(list);
}
public EXTHHeader(InputStream in) throws IOException
{
MobiCommon.logMessage("*** EXTHHeader ***");
StreamUtils.readByteArray(in, identifier);
if ((identifier[0] != 69)
||
(identifier[1] != 88)
||
(identifier[2] != 84)
||
(identifier[3] != 72))
{
throw new IOException("Expected to find EXTH header identifier"
+ " EXTH but got something else instead");
}
StreamUtils.readByteArray(in, headerLength);
if (MobiCommon.debug)
{
MobiCommon.logMessage("EXTH header length: "
+ StreamUtils.byteArrayToLong(headerLength));
}
StreamUtils.readByteArray(in, recordCount);
int count = StreamUtils.byteArrayToInt(recordCount);
MobiCommon.logMessage("EXTH record count: " + count);
recordList = new LinkedList<EXTHRecord>();
for (int i=0; i<count; i++)
{
recordList.add(new EXTHRecord(in));
}
int padding = paddingSize(dataSize());
MobiCommon.logMessage("padding size: " + padding);
for (int i=0; i<padding; i++) StreamUtils.readByte(in);
}
public int size()
{
int dataSize = dataSize();
return 12 + dataSize + paddingSize(dataSize);
}
public void recomputeFields()
{
StreamUtils.intToByteArray(size(), headerLength);
StreamUtils.intToByteArray(recordList.size(), recordCount);
}
public List<EXTHRecord> getRecordList()
{
LinkedList<EXTHRecord> list = new LinkedList<EXTHRecord>();
for (EXTHRecord rec : recordList)
{
list.add(rec.copy());
}
return list;
}
public void setRecordList(List<EXTHRecord> list)
{
recordList = new LinkedList<EXTHRecord>();
if (list != null)
{
for (EXTHRecord rec : list)
{
recordList.add(rec.copy());
}
}
recomputeFields();
}
public void removeRecordsWithType(int type)
{
boolean changed = false;
for (EXTHRecord rec : recordList)
{
if (rec.getRecordType() == type)
{
recordList.remove(rec);
changed = true;
}
}
if (changed) recomputeFields();
}
public boolean recordsWithTypeExist(int type)
{
for (EXTHRecord rec : recordList)
{
if (rec.getRecordType() == type) return true;
}
return false;
}
public void setAllRecordsWithTypeToString(int type,
String s,
String encoding)
{
boolean changed = false;
for (EXTHRecord rec : recordList)
{
if (rec.getRecordType() == type)
{
rec.setData(s, encoding);
changed = true;
}
}
if (changed) recomputeFields();
}
public void addRecord(int recType, String s, String encoding)
{
EXTHRecord rec = new EXTHRecord
(recType, StreamUtils.stringToByteArray(s, encoding));
recordList.add(rec);
recomputeFields();
}
public void addRecord(int recType, byte[] buffer)
{
recordList.add(new EXTHRecord(recType, buffer));
recomputeFields();
}
protected int dataSize()
{
int size = 0;
for (EXTHRecord rec : recordList)
{
size += rec.size();
}
return size;
}
protected int paddingSize(int dataSize)
{
int paddingSize = dataSize % 4;
if (paddingSize != 0) paddingSize = 4 - paddingSize;
return paddingSize;
}
public void write(OutputStream out) throws IOException
{
out.write(identifier);
out.write(headerLength);
out.write(recordCount);
for (EXTHRecord rec : recordList)
{
rec.write(out);
}
int padding = paddingSize(dataSize());
for (int i=0; i<padding; i++) out.write(0);
}
}

251
src/mobimeta/EXTHRecord.java

@ -0,0 +1,251 @@
package mobimeta;
import java.io.*;
import java.util.HashMap;
import java.util.HashSet;
public class EXTHRecord
{
// if a type exists in booleanTypes, then it is assumed to have boolean
// values
// if a type exists in knownTypes but not in booleanTypes, then it is
// assumed to have string values
public final static int[] booleanTypes =
{ 404 };
public final static int[] knownTypes =
{ 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
114, 118, 119, 200, 404, 501, 503, 504 };
public final static String[] knownDesc =
{ "author", "publisher", "imprint", "description", "ISBN", "subject",
"publishing date", "review", "contributor", "rights",
"subject code", "type", "source", "ASIN", "version number",
"retail price", "retail price currency", "dictionary short name",
"TTS off", "CDE type", "updated title", "ASIN" };
private static HashMap<Integer, String> typeHash;
private static HashSet<Integer> booleanTypesSet;
private byte[] recordType = { 0, 0, 0, 0 };
private byte[] recordLength = { 0, 0, 0, 0 };
private byte[] recordData = null;
static
{
typeHash = new HashMap<Integer,String>(knownTypes.length);
for (int i=0; i<knownTypes.length; i++)
typeHash.put(Integer.valueOf(knownTypes[i]), knownDesc[i]);
booleanTypesSet = new HashSet<Integer>(booleanTypes.length);
for (int i=0; i<booleanTypes.length; i++)
booleanTypesSet.add(Integer.valueOf(booleanTypes[i]));
}
public static boolean isBooleanType(int type)
{
return booleanTypesSet.contains(Integer.valueOf(type));
}
public static boolean isKnownType(int type)
{
return typeHash.containsKey(Integer.valueOf(type));
}
public static String getDescriptionForType(int type)
{
return typeHash.get(Integer.valueOf(type));
}
public EXTHRecord(int recType, String data, String characterEncoding)
{
this(recType, StreamUtils.stringToByteArray(data, characterEncoding));
}
public EXTHRecord(int recType, boolean data)
{
StreamUtils.intToByteArray(recType, recordType);
recordData = new byte[1];
recordData[0] = data?(byte)1:0;
StreamUtils.intToByteArray(size(), recordLength);
}
public EXTHRecord(int recType, byte[] data)
{
StreamUtils.intToByteArray(recType, recordType);
int len = (data==null)?0:data.length;
StreamUtils.intToByteArray(len + 8, recordLength);
recordData = new byte[len];
if (len > 0)
{
System.arraycopy(data, 0, recordData, 0, len);
}
}
public EXTHRecord(InputStream in) throws IOException
{
MobiCommon.logMessage("*** EXTHRecord ***");
StreamUtils.readByteArray(in, recordType);
StreamUtils.readByteArray(in, recordLength);
int len = StreamUtils.byteArrayToInt(recordLength);
if (len < 8) throw new IOException("Invalid EXTH record length");
recordData = new byte[len - 8];
StreamUtils.readByteArray(in, recordData);
if (MobiCommon.debug)
{
int recType = StreamUtils.byteArrayToInt(recordType);
System.out.print("EXTH record type: ");
switch (recType)
{
case 100:
MobiCommon.logMessage("author");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 101:
MobiCommon.logMessage("publisher");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 103:
MobiCommon.logMessage("description");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 104:
MobiCommon.logMessage("isbn");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 105:
MobiCommon.logMessage("subject");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 106:
MobiCommon.logMessage("publishingdate");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 109:
MobiCommon.logMessage("rights");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 113:
case 504:
MobiCommon.logMessage("asin");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 118:
MobiCommon.logMessage("retail price");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 119:
MobiCommon.logMessage("retail price currency");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 200:
MobiCommon.logMessage("dictionary short name");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
case 404:
MobiCommon.logMessage("text to speech");
int ttsflag = StreamUtils.byteArrayToInt(recordData);
MobiCommon.logMessage((ttsflag == 0)?"enabled":"disabled");
break;
case 501:
MobiCommon.logMessage("cdetype");
MobiCommon.logMessage
(StreamUtils.byteArrayToString(recordData));
break;
default:
MobiCommon.logMessage(Integer.toString(recType));
}
}
}
public int getRecordType()
{
return StreamUtils.byteArrayToInt(recordType);
}
public byte[] getData()
{
return recordData;
}
public int getDataLength()
{
return recordData.length;
}
public int size()
{
return getDataLength() + 8;
}
public void setData(String s, String encoding)
{
recordData = StreamUtils.stringToByteArray(s, encoding);
StreamUtils.intToByteArray(size(), recordLength);
}
public void setData(int value)
{
if (recordData == null)
{
recordData = new byte[4];
StreamUtils.intToByteArray(size(), recordLength);
}
StreamUtils.intToByteArray(value, recordData);
}
public void setData(boolean value)
{
if (recordData == null)
{
recordData = new byte[1];
StreamUtils.intToByteArray(size(), recordLength);
}
StreamUtils.intToByteArray(value?1:0, recordData);
}
public EXTHRecord copy()
{
return new EXTHRecord(StreamUtils.byteArrayToInt(recordType),
recordData);
}
public boolean isKnownType()
{
return isKnownType(StreamUtils.byteArrayToInt(recordType));
}
public String getTypeDescription()
{
return getDescriptionForType(StreamUtils.byteArrayToInt(recordType));
}
public void write(OutputStream out) throws IOException
{
if (MobiCommon.debug)
{
MobiCommon.logMessage("*** Write EXTHRecord ***");
MobiCommon.logMessage(StreamUtils.dumpByteArray(recordType));
MobiCommon.logMessage(StreamUtils.dumpByteArray(recordLength));
MobiCommon.logMessage(StreamUtils.dumpByteArray(recordData));
MobiCommon.logMessage("************************");
}
out.write(recordType);
out.write(recordLength);
out.write(recordData);
}
}

15
src/mobimeta/MobiCommon.java

@ -0,0 +1,15 @@
package mobimeta;
public class MobiCommon
{
public static boolean debug = false;
// safe mode avoids changing the size of the mobi header
//
public static boolean safeMode = false;
public static void logMessage(String message)
{
if (debug) System.out.println(message);
}
}

526
src/mobimeta/MobiHeader.java

@ -0,0 +1,526 @@
package mobimeta;
import java.io.*;
import java.util.*;
public class MobiHeader
{
private byte[] compression = { 0, 0 };
private byte[] unused0 = { 0, 0 };
private byte[] textLength = { 0, 0, 0, 0 };
private byte[] recordCount = { 0, 0 };
private byte[] recordSize = { 0, 0 };
private byte[] encryptionType = { 0, 0 };
private byte[] unused1 = { 0, 0 };
private byte[] identifier = { 0, 0, 0, 0 };
private byte[] headerLength = { 0, 0, 0, 0 }; // from offset 0x10
private byte[] mobiType = { 0, 0, 0, 0 };
private byte[] textEncoding = { 0, 0, 0, 0 };
private byte[] uniqueID = { 0, 0, 0, 0 };
private byte[] fileVersion = { 0, 0, 0, 0 };
private byte[] orthographicIndex = { 0, 0, 0, 0 };
private byte[] inflectionIndex = { 0, 0, 0, 0 };
private byte[] indexNames = { 0, 0, 0, 0 };
private byte[] indexKeys = { 0, 0, 0, 0 };
private byte[] extraIndex0 = { 0, 0, 0, 0 };
private byte[] extraIndex1 = { 0, 0, 0, 0 };
private byte[] extraIndex2 = { 0, 0, 0, 0 };
private byte[] extraIndex3 = { 0, 0, 0, 0 };
private byte[] extraIndex4 = { 0, 0, 0, 0 };
private byte[] extraIndex5 = { 0, 0, 0, 0 };
private byte[] firstNonBookIndex = { 0, 0, 0, 0 };
private byte[] fullNameOffset = { 0, 0, 0, 0 };
private byte[] fullNameLength = { 0, 0, 0, 0 };
private byte[] locale = { 0, 0, 0, 0 };
private byte[] inputLanguage = { 0, 0, 0, 0 };
private byte[] outputLanguage = { 0, 0, 0, 0 };
private byte[] minVersion = { 0, 0, 0, 0 };
private byte[] firstImageIndex = { 0, 0, 0, 0 };
private byte[] huffmanRecordOffset = { 0, 0, 0, 0 };
private byte[] huffmanRecordCount = { 0, 0, 0, 0 };
private byte[] huffmanTableOffset = { 0, 0, 0, 0 };
private byte[] huffmanTableLength = { 0, 0, 0, 0 };
private byte[] exthFlags = { 0, 0, 0, 0 };
private byte[] restOfMobiHeader = null;
private EXTHHeader exthHeader = null;
private byte[] remainder = null;
// end of useful data
private byte[] fullName = null;
private String characterEncoding = null;
public MobiHeader(InputStream in, long mobiHeaderSize) throws IOException
{
MobiCommon.logMessage("*** MobiHeader ***");
MobiCommon.logMessage("compression");
StreamUtils.readByteArray(in, compression);
StreamUtils.readByteArray(in, unused0);
StreamUtils.readByteArray(in, textLength);
StreamUtils.readByteArray(in, recordCount);
StreamUtils.readByteArray(in, recordSize);
MobiCommon.logMessage("encryptionType");
StreamUtils.readByteArray(in, encryptionType);
StreamUtils.readByteArray(in, unused1);
StreamUtils.readByteArray(in, identifier);
if (MobiCommon.debug)
{
MobiCommon.logMessage("identifier: "
+ StreamUtils.byteArrayToString(identifier));
}
if ((identifier[0] != 77)
||
(identifier[1] != 79)
||
(identifier[2] != 66)
||
(identifier[3] != 73))
{
throw new IOException("Did not get expected MOBI identifier");
}
// this value will determine the size of restOfMobiHeader[]
//
StreamUtils.readByteArray(in, headerLength);
int headLen = StreamUtils.byteArrayToInt(headerLength);
restOfMobiHeader = new byte[headLen + 16 - 132];
if (MobiCommon.debug)
{
MobiCommon.logMessage("headerLength: " + headLen);
}
StreamUtils.readByteArray(in, mobiType);
if (MobiCommon.debug)
{
MobiCommon.logMessage("mobiType: "
+ StreamUtils.byteArrayToInt(mobiType));
}
StreamUtils.readByteArray(in, textEncoding);
switch (StreamUtils.byteArrayToInt(textEncoding))
{
case 1252:
characterEncoding = "Cp1252";
break;
case 65001:
characterEncoding = "UTF-8";
break;
default:
characterEncoding = null;
break;
}
MobiCommon.logMessage("text encoding: " + characterEncoding);
StreamUtils.readByteArray(in, uniqueID);
StreamUtils.readByteArray(in, fileVersion);
StreamUtils.readByteArray(in, orthographicIndex);
StreamUtils.readByteArray(in, inflectionIndex);
StreamUtils.readByteArray(in, indexNames);
StreamUtils.readByteArray(in, indexKeys);
StreamUtils.readByteArray(in, extraIndex0);
StreamUtils.readByteArray(in, extraIndex1);
StreamUtils.readByteArray(in, extraIndex2);
StreamUtils.readByteArray(in, extraIndex3);
StreamUtils.readByteArray(in, extraIndex4);
StreamUtils.readByteArray(in, extraIndex5);
StreamUtils.readByteArray(in, firstNonBookIndex);
StreamUtils.readByteArray(in, fullNameOffset);
if (MobiCommon.debug)
{
MobiCommon.logMessage("full name offset: "
+ StreamUtils.byteArrayToInt(fullNameOffset));
}
StreamUtils.readByteArray(in, fullNameLength);
int fullNameLen = StreamUtils.byteArrayToInt(fullNameLength);
MobiCommon.logMessage("full name length: " + fullNameLen);
StreamUtils.readByteArray(in, locale);
StreamUtils.readByteArray(in, inputLanguage);
StreamUtils.readByteArray(in, outputLanguage);
StreamUtils.readByteArray(in, minVersion);
StreamUtils.readByteArray(in, firstImageIndex);
StreamUtils.readByteArray(in, huffmanRecordOffset);
StreamUtils.readByteArray(in, huffmanRecordCount);
StreamUtils.readByteArray(in, huffmanTableOffset);
StreamUtils.readByteArray(in, huffmanTableLength);
StreamUtils.readByteArray(in, exthFlags);
if (MobiCommon.debug)
{
MobiCommon.logMessage("exthFlags: "
+ StreamUtils.byteArrayToInt(exthFlags));
}
boolean exthExists = ((StreamUtils.byteArrayToInt(exthFlags) & 0x40)
!= 0);
MobiCommon.logMessage("exthExists: " + exthExists);
StreamUtils.readByteArray(in, restOfMobiHeader);
if (exthExists)
{
exthHeader = new EXTHHeader(in);
}
int currentOffset = 132 + restOfMobiHeader.length + exthHeaderSize();
remainder = new byte[(int)(mobiHeaderSize - currentOffset)];
StreamUtils.readByteArray(in, remainder);
int fullNameIndexInRemainder
= StreamUtils.byteArrayToInt(fullNameOffset) - currentOffset;
fullName = new byte[fullNameLen];
MobiCommon.logMessage("fullNameIndexInRemainder: "
+ fullNameIndexInRemainder);
MobiCommon.logMessage("fullNameLen: " + fullNameLen);
if ((fullNameIndexInRemainder >= 0)
&&
(fullNameIndexInRemainder < remainder.length)
&&
((fullNameIndexInRemainder + fullNameLen) <= remainder.length)
&&
(fullNameLen > 0))
{
System.arraycopy(remainder,
fullNameIndexInRemainder,
fullName,
0,
fullNameLen);
}
if (MobiCommon.debug)
{
MobiCommon.logMessage("full name: "
+ StreamUtils.byteArrayToString(fullName));
}
}
public String getCharacterEncoding()
{
return characterEncoding;
}
public String getFullName()
{
return StreamUtils.byteArrayToString(fullName, characterEncoding);
}
public void setFullName(String s)
{
byte[] fullBytes = StreamUtils.stringToByteArray(s, characterEncoding);
int len = fullBytes.length;
StreamUtils.intToByteArray(len, fullNameLength);
// the string must be terminated by 2 null bytes
// then this must end in a 4-byte boundary
//
int padding = (len + 2) % 4;
if (padding != 0) padding = 4 - padding;
padding += 2;
byte[] buffer = new byte[len + padding];
System.arraycopy(fullBytes, 0, buffer, 0, len);
for (int i=len; i<buffer.length; i++) buffer[i] = 0;
fullName = buffer;
}
public int getLocale()
{
return StreamUtils.byteArrayToInt(locale);
}
public void setLocale(int localeInt)
{
StreamUtils.intToByteArray(localeInt, locale);
}
public int getInputLanguage()
{
return StreamUtils.byteArrayToInt(inputLanguage);
}
public void setInputLanguage(int input)
{
StreamUtils.intToByteArray(input, inputLanguage);
}
public int getOutputLanguage()
{
return StreamUtils.byteArrayToInt(outputLanguage);
}
public void setOutputLanguage(int output)
{
StreamUtils.intToByteArray(output, outputLanguage);
}
public List<EXTHRecord> getEXTHRecords()
{
return (exthHeader == null) ? (new LinkedList<EXTHRecord>())
: exthHeader.getRecordList();
}
public void setEXTHRecords(List<EXTHRecord> list)
{
int flag = StreamUtils.byteArrayToInt(exthFlags) & 0xffffbf;
if ((list == null) || (list.size() == 0))
{
exthHeader = null;
StreamUtils.intToByteArray(flag, exthFlags);
}
else
{
if (exthHeader == null)
exthHeader = new EXTHHeader(list);
else
exthHeader.setRecordList(list);
StreamUtils.intToByteArray(flag | 0x40, exthFlags);
}
}
public void pack()
{
if (!MobiCommon.safeMode)
{
// dump existing remainder, set to fullName
remainder = new byte[fullName.length];
System.arraycopy(fullName, 0, remainder, 0, remainder.length);
// adjust fullNameOffset
StreamUtils.intToByteArray(132 + restOfMobiHeader.length
+ exthHeaderSize(), fullNameOffset);
}
}
public int size()
{
return 132 + restOfMobiHeader.length + exthHeaderSize() + remainder.length;
}
public String getCompression()
{
int comp = StreamUtils.byteArrayToInt(compression);
switch (comp)
{
case 1:
return "None";
case 2:
return "PalmDOC";
case 17480:
return "HUFF/CDIC";
default:
return "Unknown (" + comp + ")";
}
}
public long getTextLength()
{
return StreamUtils.byteArrayToLong(textLength);
}
public int getRecordCount()
{
return StreamUtils.byteArrayToInt(recordCount);
}
public int getRecordSize()
{
return StreamUtils.byteArrayToInt(recordSize);
}
public String getEncryptionType()
{
int enc = StreamUtils.byteArrayToInt(encryptionType);
switch (enc)
{
case 0: return "None";
case 1: return "Old Mobipocket";
case 2: return "Mobipocket";
default: return "Unknown (" + enc + ")";
}
}
public long getHeaderLength()
{
return StreamUtils.byteArrayToLong(headerLength);
}
public String getMobiType()
{
long type = StreamUtils.byteArrayToLong(mobiType);
if (type == 2)
return "Mobipocket Book";
else if (type == 3)
return "PalmDoc Book";
else if (type == 4)
return "Audio";
else if (type == 257)
return "News";
else if (type == 258)
return "News Feed";
else if (type == 259)
return "News Magazine";
else if (type == 513)
return "PICS";
else if (type == 514)
return "WORD";
else if (type == 515)
return "XLS";
else if (type == 516)
return "PPT";
else if (type == 517)
return "TEXT";
else if (type == 518)
return "HTML";
else
return "Unknown (" + type + ")";
}
public long getUniqueID()
{
return StreamUtils.byteArrayToLong(uniqueID);
}
public long getFileVersion()
{
return StreamUtils.byteArrayToLong(fileVersion);
}
public long getOrthographicIndex()
{
return StreamUtils.byteArrayToLong(orthographicIndex);
}
public long getInflectionIndex()
{
return StreamUtils.byteArrayToLong(inflectionIndex);
}
public long getIndexNames()
{
return StreamUtils.byteArrayToLong(indexNames);
}
public long getIndexKeys()
{
return StreamUtils.byteArrayToLong(indexKeys);
}
public long getExtraIndex0()
{
return StreamUtils.byteArrayToLong(extraIndex0);
}
public long getExtraIndex1()
{
return StreamUtils.byteArrayToLong(extraIndex1);
}
public long getExtraIndex2()
{
return StreamUtils.byteArrayToLong(extraIndex2);
}
public long getExtraIndex3()
{
return StreamUtils.byteArrayToLong(extraIndex3);
}
public long getExtraIndex4()
{
return StreamUtils.byteArrayToLong(extraIndex4);
}
public long getExtraIndex5()
{
return StreamUtils.byteArrayToLong(extraIndex5);
}
public long getFirstNonBookIndex()
{
return StreamUtils.byteArrayToLong(firstNonBookIndex);
}
public long getFullNameOffset()
{
return StreamUtils.byteArrayToLong(fullNameOffset);
}
public long getFullNameLength()
{
return StreamUtils.byteArrayToLong(fullNameLength);
}
public long getMinVersion()
{
return StreamUtils.byteArrayToLong(minVersion);
}
public long getHuffmanRecordOffset()
{
return StreamUtils.byteArrayToLong(huffmanRecordOffset);
}
public long getHuffmanRecordCount()
{
return StreamUtils.byteArrayToLong(huffmanRecordCount);
}
public long getHuffmanTableOffset()
{
return StreamUtils.byteArrayToLong(huffmanTableOffset);
}
public long getHuffmanTableLength()
{
return StreamUtils.byteArrayToLong(huffmanTableLength);
}
private int exthHeaderSize()
{
return (exthHeader == null)?0:exthHeader.size();
}
public void write(OutputStream out) throws IOException
{
out.write(compression);
out.write(unused0);
out.write(textLength);
out.write(recordCount);
out.write(recordSize);
out.write(encryptionType);
out.write(unused1);
out.write(identifier);
out.write(headerLength);
out.write(mobiType);
out.write(textEncoding);
out.write(uniqueID);
out.write(fileVersion);
out.write(orthographicIndex);
out.write(inflectionIndex);
out.write(indexNames);
out.write(indexKeys);
out.write(extraIndex0);
out.write(extraIndex1);
out.write(extraIndex2);
out.write(extraIndex3);
out.write(extraIndex4);
out.write(extraIndex5);
out.write(firstNonBookIndex);
out.write(fullNameOffset);
out.write(fullNameLength);
out.write(locale);
out.write(inputLanguage);
out.write(outputLanguage);
out.write(minVersion);
out.write(firstImageIndex);
out.write(huffmanRecordOffset);
out.write(huffmanRecordCount);
out.write(huffmanTableOffset);
out.write(huffmanTableLength);
out.write(exthFlags);
out.write(restOfMobiHeader);
if (exthHeader != null) exthHeader.write(out);
out.write(remainder);
}
}

292
src/mobimeta/MobiMeta.java

@ -0,0 +1,292 @@
package mobimeta;
import java.io.*;
import java.util.*;
public class MobiMeta
{
public final static int BUFFER_SIZE = 4096;
protected PDBHeader pdbHeader;
protected MobiHeader mobiHeader;
protected String characterEncoding;
protected List<EXTHRecord> exthRecords;
private File inputFile;
public MobiMeta(File f) throws MobiMetaException
{
inputFile = f;
FileInputStream in = null;
try
{
in = new FileInputStream(f);
pdbHeader = new PDBHeader(in);
mobiHeader = new MobiHeader(in, pdbHeader.getMobiHeaderSize());
exthRecords = mobiHeader.getEXTHRecords();
characterEncoding = mobiHeader.getCharacterEncoding();
}
catch (IOException e)
{
throw new MobiMetaException("Could not parse mobi file "
+ f.getAbsolutePath()
+ ": "
+ e.getMessage());
}
finally
{
if (in != null) try { in.close(); } catch (IOException e) {}
}
}
public void saveToNewFile(File outputFile) throws MobiMetaException
{
saveToNewFile(outputFile, true);
}
public void saveToNewFile(File outputFile, boolean packHeader) throws MobiMetaException
{
long readOffset = pdbHeader.getOffsetAfterMobiHeader();
if (!MobiCommon.safeMode && packHeader)
{
mobiHeader.pack();
pdbHeader.adjustOffsetsAfterMobiHeader(mobiHeader.size());
}
FileInputStream in = null;
FileOutputStream out = null;
try
{
out = new FileOutputStream(outputFile);
pdbHeader.write(out);
mobiHeader.write(out);
int bytesRead;
byte[] buffer = new byte[BUFFER_SIZE];
in = new FileInputStream(inputFile);
in.skip(readOffset);
while ((bytesRead = in.read(buffer)) != -1)
{
out.write(buffer, 0, bytesRead);
}
}
catch (IOException e)
{
throw new MobiMetaException(
"Problems encountered while writing to "
+ outputFile.getAbsolutePath() + ": "
+ e.getMessage());
}
finally
{
if (in != null) try { in.close(); } catch (IOException e) {}
if (out != null) try { out.close(); } catch (IOException e) {}
}
}
public String getCharacterEncoding()
{
return mobiHeader.getCharacterEncoding();
}
public String getFullName()
{
return mobiHeader.getFullName();
}
public void setFullName(String s)
{
mobiHeader.setFullName(s);
}
public List<EXTHRecord> getEXTHRecords()
{
return exthRecords;
}
public void setEXTHRecords()
{
mobiHeader.setEXTHRecords(exthRecords);
}
public int getLocale()
{
return mobiHeader.getLocale();
}
public int getDictInput()
{
return mobiHeader.getInputLanguage();
}
public int getDictOutput()
{
return mobiHeader.getOutputLanguage();
}
public void setLanguages(int locale, int dictInput, int dictOutput)
{
mobiHeader.setLocale(locale);
mobiHeader.setInputLanguage(dictInput);
mobiHeader.setOutputLanguage(dictOutput);
}
public String getMetaInfo()
{
StringBuffer sb = new StringBuffer();
sb.append("PDB Header\r\n");
sb.append("----------\r\n");
sb.append("Name: ");
sb.append(pdbHeader.getName());
sb.append("\r\n");
String[] attributes = getPDBHeaderAttributes();
if (attributes.length > 0)
{
sb.append("Attributes: ");
for (int i=0; i<attributes.length; i++)
{
if (i > 0) sb.append(", ");
sb.append(attributes[i]);
}
sb.append("\r\n");
}
sb.append("Version: ");
sb.append(pdbHeader.getVersion());
sb.append("\r\n");
sb.append("Creation Date: ");
sb.append(pdbHeader.getCreationDate());
sb.append("\r\n");
sb.append("Modification Date: ");
sb.append(pdbHeader.getModificationDate());
sb.append("\r\n");
sb.append("Last Backup Date: ");
sb.append(pdbHeader.getLastBackupDate());
sb.append("\r\n");
sb.append("Modification Number: ");
sb.append(pdbHeader.getModificationNumber());
sb.append("\r\n");
sb.append("App Info ID: ");
sb.append(pdbHeader.getAppInfoID());
sb.append("\r\n");
sb.append("Sort Info ID: ");
sb.append(pdbHeader.getSortInfoID());
sb.append("\r\n");
sb.append("Type: ");
sb.append(pdbHeader.getType());
sb.append("\r\n");
sb.append("Creator: ");
sb.append(pdbHeader.getCreator());
sb.append("\r\n");
sb.append("Unique ID Seed: ");
sb.append(pdbHeader.getUniqueIDSeed());
sb.append("\r\n\r\n");
sb.append("PalmDOC Header\r\n");
sb.append("--------------\r\n");
sb.append("Compression: ");
sb.append(mobiHeader.getCompression());
sb.append("\r\n");
sb.append("Text Length: ");
sb.append(mobiHeader.getTextLength());
sb.append("\r\n");
sb.append("Record Count: ");
sb.append(mobiHeader.getRecordCount());
sb.append("\r\n");
sb.append("Record Size: ");
sb.append(mobiHeader.getRecordSize());
sb.append("\r\n");
sb.append("Encryption Type: ");
sb.append(mobiHeader.getEncryptionType());
sb.append("\r\n\r\n");
sb.append("MOBI Header\r\n");
sb.append("-----------\r\n");
sb.append("Header Length: ");
sb.append(mobiHeader.getHeaderLength());
sb.append("\r\n");
sb.append("Mobi Type: ");
sb.append(mobiHeader.getMobiType());
sb.append("\r\n");
sb.append("Unique ID: ");
sb.append(mobiHeader.getUniqueID());
sb.append("\r\n");
sb.append("File Version: ");
sb.append(mobiHeader.getFileVersion());
sb.append("\r\n");
sb.append("Orthographic Index: ");
sb.append(mobiHeader.getOrthographicIndex());
sb.append("\r\n");
sb.append("Inflection Index: ");
sb.append(mobiHeader.getInflectionIndex());
sb.append("\r\n");
sb.append("Index Names: ");
sb.append(mobiHeader.getIndexNames());
sb.append("\r\n");
sb.append("Index Keys: ");
sb.append(mobiHeader.getIndexKeys());
sb.append("\r\n");
sb.append("Extra Index 0: ");
sb.append(mobiHeader.getExtraIndex0());
sb.append("\r\n");
sb.append("Extra Index 1: ");
sb.append(mobiHeader.getExtraIndex1());
sb.append("\r\n");
sb.append("Extra Index 2: ");
sb.append(mobiHeader.getExtraIndex2());
sb.append("\r\n");
sb.append("Extra Index 3: ");
sb.append(mobiHeader.getExtraIndex3());
sb.append("\r\n");
sb.append("Extra Index 4: ");
sb.append(mobiHeader.getExtraIndex4());
sb.append("\r\n");
sb.append("Extra Index 5: ");
sb.append(mobiHeader.getExtraIndex5());
sb.append("\r\n");
sb.append("First Non-Book Index: ");
sb.append(mobiHeader.getFirstNonBookIndex());
sb.append("\r\n");
sb.append("Full Name Offset: ");
sb.append(mobiHeader.getFullNameOffset());
sb.append("\r\n");
sb.append("Full Name Length: ");
sb.append(mobiHeader.getFullNameLength());
sb.append("\r\n");
sb.append("Min Version: ");
sb.append(mobiHeader.getMinVersion());
sb.append("\r\n");
sb.append("Huffman Record Offset: ");
sb.append(mobiHeader.getHuffmanRecordOffset());
sb.append("\r\n");
sb.append("Huffman Record Count: ");
sb.append(mobiHeader.getHuffmanRecordCount());
sb.append("\r\n");
sb.append("Huffman Table Offset: ");
sb.append(mobiHeader.getHuffmanTableOffset());
sb.append("\r\n");
sb.append("Huffman Table Length: ");
sb.append(mobiHeader.getHuffmanTableLength());
sb.append("\r\n");
return sb.toString();
}
private String[] getPDBHeaderAttributes()
{
LinkedList<String> list = new LinkedList<String>();
int attr = pdbHeader.getAttributes();
if ((attr & 0x02) != 0) list.add("Read-Only");
if ((attr & 0x04) != 0) list.add("Dirty AppInfoArea");
if ((attr & 0x08) != 0) list.add("Backup This Database");
if ((attr & 0x10) != 0) list.add("OK To Install Newer Over Existing Copy");
if ((attr & 0x20) != 0) list.add("Force The PalmPilot To Reset After This Database Is Installed");
if ((attr & 0x40) != 0) list.add("Don't Allow Copy Of File To Be Beamed To Other Pilot");
String[] ret = new String[list.size()];
int index = 0;
for (String s : list) ret[index++] = s;
return ret;
}
}

14
src/mobimeta/MobiMetaException.java

@ -0,0 +1,14 @@
package mobimeta;
public class MobiMetaException extends Exception
{
/**
*
*/
private static final long serialVersionUID = 1L;
public MobiMetaException(String message)
{
super(message);
}
}

164
src/mobimeta/PDBHeader.java

@ -0,0 +1,164 @@
package mobimeta;
import java.io.*;
import java.util.*;
public class PDBHeader
{
private byte[] name = { 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 };
private byte[] attributes = { 0, 0 };
private byte[] version = { 0, 0 };
private byte[] creationDate = { 0, 0, 0, 0 };
private byte[] modificationDate = { 0, 0, 0, 0 };
private byte[] lastBackupDate = { 0, 0, 0, 0 };
private byte[] modificationNumber = { 0, 0, 0, 0 };
private byte[] appInfoID = { 0, 0, 0, 0 };
private byte[] sortInfoID = { 0, 0, 0, 0 };
private byte[] type = { 0, 0, 0, 0 };
private byte[] creator = { 0, 0, 0, 0 };
private byte[] uniqueIDSeed = { 0, 0, 0, 0 };
private byte[] nextRecordListID = { 0, 0, 0, 0 };
private byte[] numRecords = { 0, 0 };
private List<RecordInfo> recordInfoList;
private byte[] gapToData = { 0, 0 };
public PDBHeader(InputStream in)
throws IOException
{
MobiCommon.logMessage("*** PDBHeader ***");
StreamUtils.readByteArray(in, name);
StreamUtils.readByteArray(in, attributes);
StreamUtils.readByteArray(in, version);
StreamUtils.readByteArray(in, creationDate);
StreamUtils.readByteArray(in, modificationDate);
StreamUtils.readByteArray(in, lastBackupDate);
StreamUtils.readByteArray(in, modificationNumber);
StreamUtils.readByteArray(in, appInfoID);
StreamUtils.readByteArray(in, sortInfoID);
StreamUtils.readByteArray(in, type);
StreamUtils.readByteArray(in, creator);
StreamUtils.readByteArray(in, uniqueIDSeed);
StreamUtils.readByteArray(in, nextRecordListID);
StreamUtils.readByteArray(in, numRecords);
int recordCount = StreamUtils.byteArrayToInt(numRecords);
MobiCommon.logMessage("numRecords: " + recordCount);
recordInfoList = new LinkedList<RecordInfo>();
for (int i=0; i<recordCount; i++)
{
recordInfoList.add(new RecordInfo(in));
}
StreamUtils.readByteArray(in, gapToData);
}
public long getMobiHeaderSize()
{
return (recordInfoList.size() > 1) ? (recordInfoList.get(1)
.getRecordDataOffset() - recordInfoList.get(0)
.getRecordDataOffset()) : 0;
}
public long getOffsetAfterMobiHeader()
{
return (recordInfoList.size() > 1) ? recordInfoList.get(1)
.getRecordDataOffset() : 0;
}
public void adjustOffsetsAfterMobiHeader(int newMobiHeaderSize)
{
if (recordInfoList.size() < 2) return;
int delta = (int)(newMobiHeaderSize - getMobiHeaderSize());
int len = recordInfoList.size();
for (int i=1; i<len; i++)
{
RecordInfo rec = recordInfoList.get(i);
long oldOffset = rec.getRecordDataOffset();
rec.setRecordDataOffset(oldOffset + delta);
}
}
public void write(OutputStream out) throws IOException
{
out.write(name);
out.write(attributes);
out.write(version);
out.write(creationDate);
out.write(modificationDate);
out.write(lastBackupDate);
out.write(modificationNumber);
out.write(appInfoID);
out.write(sortInfoID);
out.write(type);
out.write(creator);
out.write(uniqueIDSeed);
out.write(nextRecordListID);
out.write(numRecords);
for (RecordInfo rec : recordInfoList) rec.write(out);
out.write(gapToData);
}
public String getName()
{
return StreamUtils.byteArrayToString(name);
}
public int getAttributes()
{
return StreamUtils.byteArrayToInt(attributes);
}
public int getVersion()
{
return StreamUtils.byteArrayToInt(version);
}
public long getCreationDate()
{
return StreamUtils.byteArrayToLong(creationDate);
}
public long getModificationDate()
{
return StreamUtils.byteArrayToLong(modificationDate);
}
public long getLastBackupDate()
{
return StreamUtils.byteArrayToLong(lastBackupDate);
}
public long getModificationNumber()
{
return StreamUtils.byteArrayToLong(modificationNumber);
}
public long getAppInfoID()
{
return StreamUtils.byteArrayToLong(appInfoID);
}
public long getSortInfoID()
{
return StreamUtils.byteArrayToLong(sortInfoID);
}
public long getType()
{
return StreamUtils.byteArrayToLong(type);
}
public long getCreator()
{
return StreamUtils.byteArrayToLong(creator);
}
public long getUniqueIDSeed()
{
return StreamUtils.byteArrayToLong(uniqueIDSeed);
}
}

41
src/mobimeta/RecordInfo.java

@ -0,0 +1,41 @@
package mobimeta;
import java.io.*;
public class RecordInfo
{
private byte[] recordDataOffset = { 0, 0, 0, 0 };
private byte recordAttributes = 0;
private byte[] uniqueID = { 0, 0, 0 };
public RecordInfo(InputStream in)
throws IOException
{
StreamUtils.readByteArray(in, recordDataOffset);
recordAttributes = StreamUtils.readByte(in);
StreamUtils.readByteArray(in, uniqueID);
if (MobiCommon.debug)
{
MobiCommon.logMessage("RecordInfo uniqueID: "
+ StreamUtils.byteArrayToInt(uniqueID));
}
}
public long getRecordDataOffset()
{
return StreamUtils.byteArrayToLong(recordDataOffset);
}
public void setRecordDataOffset(long newOffset)
{
StreamUtils.longToByteArray(newOffset, recordDataOffset);
}
public void write(OutputStream out) throws IOException
{
out.write(recordDataOffset);
out.write(recordAttributes);
out.write(uniqueID);
}
}

183
src/mobimeta/StreamUtils.java

@ -0,0 +1,183 @@
package mobimeta;
import java.io.*;
public class StreamUtils
{
public static String readCString(InputStream in, int len)
throws IOException
{
byte[] buffer = new byte[len];
int bytesLeft = len;
int offset = 0;
while (bytesLeft > 0)
{
int bytesRead = in.read(buffer, offset, bytesLeft);
if (bytesRead == -1)
throw new IOException("Supposed to read a "
+ len
+ " byte C string, but could not");
offset += bytesRead;
bytesLeft -= bytesRead;
}
String s = byteArrayToString(buffer);
MobiCommon.logMessage("readCString: " + s);
return s;
}
public static byte readByte(InputStream in)
throws IOException
{
int b = in.read();
if (b == -1)
throw new IOException("Supposed to read a byte, but could not");
MobiCommon.logMessage("readByte: " + b);
return (byte)(b & 0xff);
}
public static void readByteArray(InputStream in, byte[] buffer)
throws IOException
{
int len = buffer.length;
int bytesLeft = len;
int offset = 0;
while (bytesLeft > 0)
{
int bytesRead = in.read(buffer, offset, bytesLeft);
if (bytesRead == -1)
throw new IOException("Supposed to read a "
+ len
+ " byte array, but could not");
offset += bytesRead;
bytesLeft -= bytesRead;
}
if (MobiCommon.debug)
{
MobiCommon.logMessage(dumpByteArray(buffer));
}
}
public static String byteArrayToString(byte[] buffer)
{
return byteArrayToString(buffer, null);
}
public static String byteArrayToString(byte[] buffer, String encoding)
{
int len = buffer.length;
int zeroIndex = -1;
for (int i=0; i<len; i++)
{
byte b = buffer[i];
if (b == 0)
{
zeroIndex = i;
break;
}
}
if (encoding != null)
{
try
{
if (zeroIndex == -1)
return new String(buffer, encoding);
else
return new String(buffer, 0, zeroIndex, encoding);
}
catch (java.io.UnsupportedEncodingException e)
{
// let it fall through and use the default encoding
}
}
if (zeroIndex == -1)
return new String(buffer);
else
return new String(buffer, 0, zeroIndex);
}
public static int byteArrayToInt(byte[] buffer)
{
int total = 0;
int len = buffer.length;
for (int i=0; i<len; i++)
{
total = (total << 8) + (buffer[i] & 0xff);
}
return total;
}
public static long byteArrayToLong(byte[] buffer)
{
long total = 0;
int len = buffer.length;
for (int i=0; i<len; i++)
{
total = (total << 8) + (buffer[i] & 0xff);
}
return total;
}
public static void intToByteArray(int value, byte[] dest)
{
int lastIndex = dest.length - 1;
for (int i=lastIndex; i >=0; i--)
{
dest[i] = (byte)(value & 0xff);
value = value >> 8;
}
}
public static void longToByteArray(long value, byte[] dest)
{
int lastIndex = dest.length - 1;
for (int i=lastIndex; i >=0; i--)
{
dest[i] = (byte)(value & 0xff);
value = value >> 8;
}
}
public static byte[] stringToByteArray(String s)
{
return stringToByteArray(s, null);
}
public static byte[] stringToByteArray(String s, String encoding)
{
if (encoding != null)
{
try
{
return s.getBytes(encoding);
}
catch (UnsupportedEncodingException e)
{
// let if fall through to use the default character encoding
}
}
return s.getBytes();
}
public static String dumpByteArray(byte[] buffer)
{
StringBuffer sb = new StringBuffer();
sb.append("{ ");
int len = buffer.length;
for (int i=0; i<len; i++)
{
if (i > 0) sb.append(", ");
sb.append(buffer[i] & 0xff);
}
sb.append(" }");
return sb.toString();
}
}
Loading…
Cancel
Save