Java tutorial
/* * $Id$ * Copyright (C) 2006 Klaus Reimer <k@ailis.de> * * 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. */ package de.ailis.wlandsuite.game.parts; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.dom4j.Element; import de.ailis.wlandsuite.common.exceptions.GameException; import de.ailis.wlandsuite.game.chartable.CharTable; import de.ailis.wlandsuite.io.SeekableInputStream; import de.ailis.wlandsuite.io.SeekableOutputStream; import de.ailis.wlandsuite.utils.StringUtils; import de.ailis.wlandsuite.utils.XmlUtils; /** * All the strings of a map. Be careful when you delete a string because other * strings will get a new index and you have to correct all references to these * strings. * * @author Klaus Reimer (k@ailis.de) * @version $Revision$ */ public class Strings extends ArrayList<String> { /** Serial version UID */ private static final long serialVersionUID = 1697322917088367990L; /** * Constructs a new String list */ public Strings() { super(); } /** * Constructor * * @param capacity * The initial capacity */ public Strings(final int capacity) { super(capacity); } /** * Creates and returns a new Strings object by reading all the strings from * the specified stream. * * @param stream * The stream to read the strings from * @param endOffset * The definite end of the strings block * @return The strings * @throws IOException * When file operation fails. */ public static Strings read(final SeekableInputStream stream, final int endOffset) throws IOException { long startOffset; Strings strings; List<Integer> stringOffsets; int tmp, quantity; // Remember the start offset startOffset = stream.tell(); // Create the strings object strings = new Strings(); // Read the character table final CharTable charTable = new CharTable(stream); // Read the string offsets tmp = stream.readWord(); quantity = tmp / 2; stringOffsets = new ArrayList<Integer>(quantity); stringOffsets.add(tmp); for (int i = 1; i < quantity; i++) { tmp = stream.readWord(); if ((tmp + startOffset + 60 >= endOffset) || (tmp < stringOffsets.get(i - 1))) { // The last offset may be corrupt. That's ok. If it's not // the last offset then throw an exception. if (i == quantity - 1) { continue; } else { throw new GameException("Error parsing strings"); } } stringOffsets.add(tmp); } // Read the strings for (int i = 0, max = stringOffsets.size(); i < max; i++) { stream.seek(stringOffsets.get(i) + 60 + startOffset); readStringGroup(stream, charTable, strings, endOffset); } // Return the strings return strings; } /** * Reads a string group from the specified stream. The stream must be * positioned at the beginning of the string group. The strings are * completely decrypted by using the specified char table. Strings are added * to the specified strings array. * * @param stream * The stream to read the string group from * @param charTable * The character table * @param strings * The string list to add the strings to * @param endOffset * The definite end of the strings block * @throws IOException * When file operation fails. */ private static void readStringGroup(final SeekableInputStream stream, final CharTable charTable, final List<String> strings, final int endOffset) throws IOException { for (int j = 0; j < 4; j++) { boolean upper = false; boolean high = false; final StringBuilder string = new StringBuilder(); outer: while (true) { if (stream.tell() > endOffset) { return; } final int index = stream.readBits(5, true); switch (index) { case 0x1f: high = true; break; case 0x1e: upper = true; break; default: final int character = charTable.getCharacter(index + (high ? 0x1e : 0)); if (character == 0) { break outer; } String s = new String(new byte[] { (byte) character }, "ASCII"); if (upper) s = s.toUpperCase(); string.append(s); upper = false; high = false; } } strings.add(string.toString()); } } /** * Writes the strings to the specified stream. * * @param stream * The stream to write the strings to * @throws IOException * When file operation fails. */ public void write(final SeekableOutputStream stream) throws IOException { CharTable charTable; ByteArrayOutputStream stringStream; byte[] strings; List<Integer> offsets; int oldSize; int offset; int stringNo; SeekableOutputStream bitStream; // Create and write the char table charTable = new CharTable(); for (final String string : this) { charTable.add(string); charTable.add(0); } charTable.write(stream); // Write the strings to temporary buffer and build the offset table stringStream = new ByteArrayOutputStream(); offsets = new ArrayList<Integer>(size() / 4 + 1); offset = 0; oldSize = 0; stringNo = 0; stringStream = new ByteArrayOutputStream(); bitStream = new SeekableOutputStream(stringStream); for (final String string : this) { writeString(bitStream, string, charTable); stringNo++; if (stringNo % 4 == 0) { bitStream.flush(true); offsets.add(offset); offset += stringStream.size() - oldSize; oldSize = stringStream.size(); bitStream = new SeekableOutputStream(stringStream); } } if (stringNo % 4 != 0) { bitStream.flush(true); offsets.add(offset); } strings = stringStream.toByteArray(); // Write the correct offsets to the stream for (int i = 0, max = offsets.size(); i < max; i++) { stream.writeWord(offsets.get(i) + max * 2); } // Write the strings to the stream stream.write(strings); } /** * Writes a string into the specified output stream. * * @param stream * The output stream * @param string * The string to write * @param charTable * The character table * @throws IOException * When file operation fails. */ private void writeString(final SeekableOutputStream stream, final String string, final CharTable charTable) throws IOException { for (byte b : string.getBytes("ASCII")) { // Handle upper case characters; if (b >= 65 && b <= 90) { stream.writeBits(0x1e, 5, true); b += 32; } // Get the character index in the char table int index = charTable.getIndex(b); if (index == -1) { throw new GameException("Unable to find index for character " + b); } // Handle high characters if (index >= 0x1e) { stream.writeBits(0x1f, 5, true); index -= 0x1e; } // Write the character stream.writeBits(index, 5, true); } stream.writeBits(charTable.getIndex(0), 5, true); } /** * Creates and returns a new Strings object read from the specified XML * element. * * @param element * The XML element * @return The Strings object */ public static Strings read(final Element element) { Strings strings; // Create new strings object strings = new Strings(element.elements().size()); // Read all the strings for (final Object subElement : element.elements("string")) { Element string; String text; int id; string = (Element) subElement; text = StringUtils.unescape(string.getText(), "ASCII"); id = StringUtils.toInt(string.attributeValue("id")); if (id == strings.size()) { strings.add(text); } else if (id < strings.size()) { strings.set(id, text); } else { for (int i = strings.size(); i < id; i++) { strings.add(""); } strings.add(text); } } // Return the newly created Strings object return strings; } /** * Returns the strings as XML. * * @return The strings as XML */ public Element toXml() { Element element, subElement; int stringNo; // Create the root XML element element = XmlUtils.createElement("strings"); // Add all the strings stringNo = 0; for (final String string : this) { // Create and append string element subElement = XmlUtils.createElement("string"); subElement.addAttribute("id", Integer.toString(stringNo)); subElement.addText(StringUtils.escape(string, "ASCII")); element.add(subElement); stringNo++; } // Return the XML element return element; } }