001: /*
002: * Copyright (c) 2007, intarsys consulting GmbH
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * - Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * - Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * - Neither the name of intarsys nor the names of its contributors may be used
015: * to endorse or promote products derived from this software without specific
016: * prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGE.
029: */
030: package de.intarsys.pdf.cos;
031:
032: import java.io.UnsupportedEncodingException;
033: import java.util.Arrays;
034:
035: import de.intarsys.pdf.encoding.PDFDocEncoding;
036: import de.intarsys.tools.hex.HexTools;
037:
038: /**
039: * The string representation for a pdf document
040: *
041: */
042: public class COSString extends COSPrimitiveObject implements Comparable {
043: private static String LINE_SEPARATOR = System
044: .getProperty("line.separator"); //$NON-NLS-1$
045:
046: /**
047: * Create a {@link COSString} from <code>bytes</code>.
048: *
049: * @param bytes
050: * @return The new {@link COSString}
051: */
052: public static COSString create(byte[] bytes) {
053: if (bytes == null) {
054: throw new NullPointerException(
055: "COSString value can't be null"); //$NON-NLS-1$
056: }
057: return new COSString(bytes);
058: }
059:
060: /**
061: * Create a {@link COSString} from <code>string</code>.
062: *
063: * @param string
064: * @return The new {@link COSString}
065: */
066: public static COSString create(String string) {
067: if (string == null) {
068: throw new NullPointerException(
069: "COSString value can't be null"); //$NON-NLS-1$
070: }
071: return new COSString(string);
072: }
073:
074: /**
075: * Create a {@link COSString} from <code>bytes</code> in hex
076: * representation.
077: *
078: * @param bytes
079: * @return The new {@link COSString}
080: */
081: public static COSString createHex(byte[] bytes) {
082: if (bytes == null) {
083: throw new NullPointerException(
084: "COSString value can't be null"); //$NON-NLS-1$
085: }
086: COSString result = new COSString(bytes);
087: result.setHexMode(true);
088: return result;
089: }
090:
091: /**
092: * Create a {@link COSString} from <code>string</code> in hex
093: * representation.
094: *
095: * @param string
096: * @return The new {@link COSString}
097: */
098: public static COSString createHex(String string) {
099: if (string == null) {
100: throw new NullPointerException(
101: "COSString value can't be null"); //$NON-NLS-1$
102: }
103: COSString result = new COSString(string);
104: result.setHexMode(true);
105: return result;
106: }
107:
108: /**
109: * Create a {@link COSString} from <code>string</code>, escaoing all
110: * newlines.
111: *
112: * @param string
113: * @return The new {@link COSString}
114: */
115: public static COSString createMultiLine(String string) {
116: return new COSString(toPDFString(string));
117: }
118:
119: protected static String toJavaString(String value) {
120: StringBuilder result = new StringBuilder();
121: int length = value.length();
122: boolean crFound = false;
123: for (int i = 0; i < length; i++) {
124: char c = value.charAt(i);
125: if (c == '\r') {
126: if (crFound) {
127: result.append(LINE_SEPARATOR);
128: }
129: crFound = true;
130: } else if (c == '\n') {
131: crFound = false;
132: } else {
133: if (crFound) {
134: result.append(LINE_SEPARATOR);
135: }
136: result.append(c);
137: crFound = false;
138: }
139: }
140: return result.toString();
141: }
142:
143: protected static String toPDFString(String value) {
144: StringBuilder result = new StringBuilder();
145: int length = value.length();
146: for (int i = 0; i < length; i++) {
147: char c = value.charAt(i);
148: if (c == '\r') {
149: // ignore
150: } else if (c == '\n') {
151: result.append('\r');
152: } else {
153: result.append(c);
154: }
155: }
156: return result.toString();
157: }
158:
159: // the bytes that make up the string
160: private byte[] bytes;
161:
162: // cached hash code
163: private int hash = 0;
164:
165: // flag if this string has to be written in hex representation
166: private boolean hexMode = false;
167:
168: // the decoded string representation of the bytes (lazy)
169: private String string;
170:
171: protected COSString(byte[] newBytes) {
172: super ();
173: bytes = newBytes;
174: }
175:
176: protected COSString(COSObject object) {
177: super (object);
178: COSString cosString = (COSString) object;
179: this .bytes = cosString.bytes;
180: this .hash = cosString.hash;
181: this .hexMode = cosString.hexMode;
182: this .string = cosString.string;
183: }
184:
185: protected COSString(String newString) {
186: super ();
187: string = newString;
188: }
189:
190: /*
191: * (non-Javadoc)
192: *
193: * @see de.intarsys.pdf.cos.COSObject#accept(de.intarsys.pdf.cos.ICOSObjectVisitor)
194: */
195: public java.lang.Object accept(ICOSObjectVisitor visitor)
196: throws COSVisitorException {
197: return visitor.visitFromString(this );
198: }
199:
200: /*
201: * (non-Javadoc)
202: *
203: * @see de.intarsys.pdf.cos.COSObject#asString()
204: */
205: public COSString asString() {
206: return this ;
207: }
208:
209: /*
210: * (non-Javadoc)
211: *
212: * @see de.intarsys.pdf.cos.COSObject#basicToString()
213: */
214: protected String basicToString() {
215: if (isHexMode()) {
216: return "<" + hexStringValue() + ">"; //$NON-NLS-1$ //$NON-NLS-2$
217: } else {
218: return "(" + stringValue() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
219: }
220: }
221:
222: /**
223: * Show a hex encoded representation of the strings content.
224: *
225: * @return Show a hex encoded representation of the strings content.
226: */
227: public String hexStringValue() {
228: return HexTools.bytesToHexString(byteValue());
229: }
230:
231: /**
232: * The bytes that make up this string.
233: *
234: * @return The bytes that make up this string.
235: */
236: public byte[] byteValue() {
237: if (bytes == null) {
238: bytes = encode();
239: }
240: return bytes;
241: }
242:
243: /*
244: * (non-Javadoc)
245: *
246: * @see java.lang.Comparable#compareTo(java.lang.Object)
247: */
248: public int compareTo(Object o) {
249: if (!(o instanceof COSString)) {
250: throw new ClassCastException(
251: "must compare with a COSString"); //$NON-NLS-1$
252: }
253: byte[] this Bytes = byteValue();
254: byte[] otherBytes = ((COSString) o).byteValue();
255: for (int i = 0; (i < this Bytes.length)
256: && (i < otherBytes.length); i++) {
257: if (this Bytes[i] < otherBytes[i]) {
258: return -1;
259: }
260: if (this Bytes[i] > otherBytes[i]) {
261: return 1;
262: }
263: }
264: if (this Bytes.length < otherBytes.length) {
265: return -1;
266: }
267: if (this Bytes.length > otherBytes.length) {
268: return 1;
269: }
270: return 0;
271: }
272:
273: /*
274: * (non-Javadoc)
275: *
276: * @see de.intarsys.pdf.cos.COSObject#copyBasic()
277: */
278: protected COSObject copyBasic() {
279: COSString result;
280: if (hexMode) {
281: if (bytes == null) {
282: result = createHex(stringValue());
283: } else {
284: result = createHex(byteValue());
285: }
286: } else {
287: if (bytes == null) {
288: result = create(stringValue());
289: } else {
290: result = create(byteValue());
291: }
292: }
293: return result;
294: }
295:
296: /**
297: * decode the string from the byte value in the given encoding
298: *
299: * @return the decoded string
300: */
301: protected String decode() {
302: /*
303: * UTF-16 Test: String starts with 0xfefff
304: */
305:
306: // extend test to undocumented format written by nitro pdf
307: if (bytes.length >= 2) {
308: if ((bytes[0] == (byte) 0xfe) && (bytes[1] == (byte) 0xff)) {
309: try {
310: return new String(bytes, 2, bytes.length - 2,
311: "UTF-16BE"); //$NON-NLS-1$
312: } catch (UnsupportedEncodingException e) {
313: return PDFDocEncoding.UNIQUE.decode(bytes);
314: }
315: } else if ((bytes[0] == (byte) 0xff)
316: && (bytes[1] == (byte) 0xfe)) {
317: try {
318: return new String(bytes, 2, bytes.length - 2,
319: "UTF-16LE"); //$NON-NLS-1$
320: } catch (UnsupportedEncodingException e) {
321: return PDFDocEncoding.UNIQUE.decode(bytes);
322: }
323: }
324: }
325: return PDFDocEncoding.UNIQUE.decode(bytes);
326: }
327:
328: /**
329: * encode the string in byte representation using the correct encoding
330: *
331: * @return the encoded string
332: */
333: protected byte[] encode() {
334: return PDFDocEncoding.UNIQUE.encode(string);
335: }
336:
337: /*
338: * (non-Javadoc)
339: *
340: * @see java.lang.Object#equals(java.lang.Object)
341: */
342: public boolean equals(Object o) {
343: if (!(o instanceof COSString)) {
344: return false;
345: }
346: return Arrays.equals(byteValue(), ((COSString) o).byteValue());
347: }
348:
349: /*
350: * (non-Javadoc)
351: *
352: * @see java.lang.Object#hashCode()
353: */
354: public int hashCode() {
355: if (bytes == null) {
356: return super .hashCode();
357: }
358: int h = hash;
359: if (h == 0) {
360: for (int i = 0; i < bytes.length; i++) {
361: h = (31 * h) + bytes[i];
362: }
363: hash = h;
364: }
365: return h;
366: }
367:
368: /**
369: * <code>true</code> if this string has to be saved as hex representation
370: *
371: * @return <code>true</code> if this string has to be saved as hex
372: * representation
373: */
374: public boolean isHexMode() {
375: return hexMode;
376: }
377:
378: /**
379: * A Java {@link String} with correctly expanded newlines.
380: *
381: * @return A Java {@link String} with correctly expanded newlines.
382: */
383: public String multiLineStringValue() {
384: return toJavaString(stringValue());
385: }
386:
387: /*
388: * (non-Javadoc)
389: *
390: * @see de.intarsys.pdf.cos.COSObject#restoreState(java.lang.Object)
391: */
392: public void restoreState(Object object) {
393: super .restoreState(object);
394: COSString cosString = (COSString) object;
395: this .bytes = cosString.bytes;
396: this .hash = cosString.hash;
397: this .hexMode = cosString.hexMode;
398: this .string = cosString.string;
399: }
400:
401: /*
402: * (non-Javadoc)
403: *
404: * @see de.intarsys.tools.objectsession.ISaveStateSupport#saveState()
405: */
406: public Object saveState() {
407: return new COSString(this );
408: }
409:
410: /**
411: * Set the flag if this is written in hex representation
412: *
413: * @param newHexMode
414: * <code>true</code> if this is written in hex representation
415: */
416: public void setHexMode(boolean newHexMode) {
417: hexMode = newHexMode;
418: }
419:
420: /**
421: * The Java {@link String} representation of the receiver
422: *
423: * @return The Java {@link String} representation of the receiver
424: */
425: public String stringValue() {
426: if (string == null) {
427: string = decode();
428: }
429: return string;
430: }
431: }
|