1/*
2  Copyright (C) 2000-2008 SKYRIX Software AG
3  Copyright (C) 2007-2008 Helge Hess
4
5  This file is part of SOPE.
6
7  SOPE is free software; you can redistribute it and/or modify it under
8  the terms of the GNU Lesser General Public License as published by the
9  Free Software Foundation; either version 2, or (at your option) any
10  later version.
11
12  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13  WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15  License for more details.
16
17  You should have received a copy of the GNU Lesser General Public
18  License along with SOPE; see the file COPYING.  If not, write to the
19  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20  02111-1307, USA.
21*/
22
23#include <NGStreams/NGCTextStream.h>
24#include <NGStreams/NGStreamExceptions.h>
25#include <NGStreams/NGFileStream.h>
26#include <NGExtensions/NGCharBuffers.h>
27#include "common.h"
28
29static NSString *NGNewLineString = @"\n";
30
31@interface _NGCTextStreamLineEnumerator : NSEnumerator
32{
33  NGCTextStream *stream;
34}
35- (id)initWithTextStream:(NGCTextStream *)_stream;
36- (id)nextObject;
37@end
38
39NGStreams_DECLARE id<NGExtendedTextInputStream>  NGTextIn  = nil;
40NGStreams_DECLARE id<NGExtendedTextOutputStream> NGTextOut = nil;
41NGStreams_DECLARE id<NGExtendedTextOutputStream> NGTextErr = nil;
42
43@implementation NGCTextStream
44
45// stdio
46
47NGStreams_DECLARE void NGInitTextStdio(void) {
48  static BOOL isInitialized = NO;
49  if (!isInitialized) {
50    isInitialized = YES;
51
52    NGInitStdio();
53
54    NGTextIn  = [(NGCTextStream *)[NGCTextStream alloc]
55                    initWithSource:(id<NGStream>)NGIn];
56    NGTextOut = [(NGCTextStream *)[NGCTextStream alloc]
57                    initWithSource:(id<NGStream>)NGOut];
58    NGTextErr = [(NGCTextStream *)[NGCTextStream alloc]
59                    initWithSource:(id<NGStream>)NGErr];
60  }
61}
62
63+ (void)_flushForExit:(NSNotification *)_notification {
64  // [NGTextIn  flush];
65  [NGTextIn  release]; NGTextIn  = nil;
66  [NGTextOut flush]; [NGTextOut release]; NGTextOut = nil;
67  [NGTextErr flush]; [NGTextErr release]; NGTextErr = nil;
68}
69
70static void _flushAtExit(void) {
71  // [NGTextIn flush];
72  [NGTextIn  release]; NGTextIn  = nil;
73  [NGTextOut flush]; [NGTextOut release]; NGTextOut = nil;
74  [NGTextErr flush]; [NGTextErr release]; NGTextErr = nil;
75}
76
77+ (void)initialize {
78  BOOL isInitialized = NO;
79  if (!isInitialized) {
80    isInitialized = YES;
81
82    atexit(_flushAtExit);
83  }
84}
85
86// system text stream
87
88+ (id)textStreamWithInputSource:(id<NGInputStream>)_s {
89  if (_s == nil) return nil;
90  return [[(NGCTextStream *)[self alloc] initWithInputSource:_s] autorelease];
91}
92+ (id)textStreamWithOutputSource:(id<NGOutputStream>)_s {
93  if (_s == nil) return nil;
94  return [[(NGCTextStream *)[self alloc] initWithOutputSource:_s] autorelease];
95}
96+ (id)textStreamWithSource:(id<NGStream>)_stream {
97  if (_stream == nil) return nil;
98  return [[(NGCTextStream *)[self alloc] initWithSource:_stream] autorelease];
99}
100
101- (id)initWithSource:(id<NGStream>)_stream {
102  if (_stream == nil) {
103    [self release];
104    return nil;
105  }
106  if ((self = [super init]) != nil) {
107    self->source = [_stream retain];
108
109    /* On MacOS 10.5 this is per default 30 aka MacOS Roman */
110    self->encoding = [NSString defaultCStringEncoding];
111
112#ifdef __APPLE__
113    //#  warning no selector caching on MacOSX ...
114#else
115    /* check whether we are dealing with a proxy .. */
116    if ([source isKindOfClass:[NSObject class]]) {
117      self->readBytes   = (NGIOReadMethodType)
118        [(NSObject *)source methodForSelector:@selector(readBytes:count:)];
119      self->writeBytes  = (NGIOWriteMethodType)
120        [(NSObject *)source methodForSelector:@selector(writeBytes:count:)];
121      self->flushBuffer = (BOOL (*)(id,SEL))
122        [(NSObject *)source methodForSelector:@selector(flush)];
123    }
124#endif
125  }
126  return self;
127}
128- (id)initWithInputSource:(id<NGInputStream>)_source {
129  return [self initWithSource:(id)_source];
130}
131- (id)initWithOutputSource:(id<NGOutputStream>)_source {
132  return [self initWithSource:(id)_source];
133}
134
135- (void)dealloc {
136  [self->source release];
137  self->readBytes   = NULL;
138  self->writeBytes  = NULL;
139  self->flushBuffer = NULL;
140  [super dealloc];
141}
142
143/* accessors */
144
145- (id<NGStream>)source {
146  return self->source;
147}
148- (int)fileDescriptor {
149  return [(id)[self source] fileDescriptor];
150}
151
152- (BOOL)isOpen {
153  return [(id)[self source] isOpen];
154}
155
156/* operations */
157
158- (BOOL)close {
159  return [self->source close];
160}
161
162/* NGTextInputStream */
163
164- (unichar)readCharacter {
165  return [self readChar];
166}
167
168- (unsigned char)readChar {
169  unsigned char c;
170  unsigned res;
171
172  if (readBytes) {
173    res = readBytes(self->source, @selector(readBytes:count:),
174                    &c, sizeof(unsigned char));
175  }
176  else
177    res = [self->source readBytes:&c count:sizeof(unsigned char)];
178
179  if (res == NGStreamError) {
180    [self setLastException:[self->source lastException]];
181    return -1;
182  }
183
184  return c;
185}
186
187/* TODO: fix exception handling */
188
189- (NSString *)readLineAsString {
190  NGCharBuffer8   buffer = NULL;
191  unsigned char   c;
192
193  *(&buffer) = NGCharBuffer8_new(128);
194
195  NS_DURING {
196    unsigned int res;
197
198    if (readBytes) {
199      do {
200        res = self->readBytes(source, @selector(readBytes:count:),
201                        &c, sizeof(unsigned char));
202        if (res != 1) [[self->source lastException] raise];
203
204        if (c == '\r') {
205          res = readBytes(source, @selector(readBytes:count:),
206                          &c, sizeof(unsigned char));
207          if (res != 1) [[self->source lastException] raise];
208        }
209
210        if ((c != '\n') && (c != 0)) {
211          NSAssert1(c != 0, @"tried to add '0' character to buffer '%s' ..",
212                    buffer->buffer);
213          NGCharBuffer8_addChar(buffer, c);
214        }
215      }
216      while ((c != '\n') && (c != 0));
217    }
218    else {
219      do {
220        res = [self->source readBytes:&c count:sizeof(unsigned char)];
221	/* TODO: raises exception */
222        if (res != 1) [[self->source lastException] raise];
223        if (c == '\r') {
224          res = [self->source readBytes:&c count:sizeof(unsigned char)];
225          if (res != 1) [[self->source lastException] raise];
226        }
227
228        if ((c != '\n') && (c != 0))
229          NGCharBuffer8_addChar(buffer, c);
230      }
231      while ((c != '\n') && (c != 0));
232    }
233  }
234  NS_HANDLER {
235    if ([localException isKindOfClass:[NGEndOfStreamException class]]) {
236      if (buffer->length == 0) {
237        NGCharBuffer8_dealloc(buffer);
238        buffer = NULL;
239      }
240    }
241    else
242      [localException raise];
243  }
244  NS_ENDHANDLER;
245
246  return buffer ? NGCharBuffer8_makeStringAndDealloc(buffer) : (NSString *)nil;
247}
248
249- (NSEnumerator *)lineEnumerator {
250  return [[[_NGCTextStreamLineEnumerator alloc]
251                                         initWithTextStream:self]
252                                         autorelease];
253}
254
255
256/* NGTextOutputStream */
257
258- (BOOL)writeCharacter:(unichar)_character {
259  unsigned char c;
260  unsigned res;
261
262  if (_character > ((sizeof(unsigned char) * 256) - 1)) {
263    // character is not in range of maximum system encoding
264    [NSException raise:@"NGCTextStreamEncodingException"
265                 format:
266                   @"called writeCharacter: with character code (0x%X)"
267                   @" exceeding the maximum system character code (0x%X)",
268                   _character, (int)((sizeof(unsigned char) * 256) - 1)];
269  }
270
271  c = _character;
272
273  if (self->writeBytes != NULL) {
274    res = self->writeBytes(self->source, @selector(writeBytes:count:),
275			   &c, sizeof(unsigned char));
276  }
277  else
278    res = [self->source writeBytes:&c count:sizeof(unsigned char)];
279
280  if (res == NGStreamError) {
281    [self setLastException:[self->source lastException]];
282    return NO;
283  }
284
285  return YES;
286}
287
288- (BOOL)writeString:(NSString *)_string {
289  unsigned char *str, *buf;
290  unsigned toGo;
291
292#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1040 || (GNUSTEP && OS_API_VERSION(100400,GS_API_LATEST))
293  NSData *d;
294
295  if ((toGo = [_string lengthOfBytesUsingEncoding:self->encoding]) == 0)
296    return YES;
297
298  d = [_string dataUsingEncoding: self->encoding
299            allowLossyConversion: NO];
300
301  if (!d) {
302    NSLog(@"ERROR(%s): failed to extract data using encoding(%i)"
303          @" from NSString: '%@'\n", __PRETTY_FUNCTION__,
304          self->encoding, _string);
305    return NO;
306  }
307
308  buf = str = calloc(toGo + 1, sizeof(unsigned char));
309  [d getBytes: str  length: toGo];
310  str[toGo] = '\0';
311#else
312  if ((toGo = [_string cStringLength]) == 0)
313    return YES;
314
315  buf = str = calloc(toGo + 2, sizeof(unsigned char));
316  [_string getCString:(char *)str];
317  str[toGo] = '\0';
318#endif
319
320  NS_DURING {
321    while (toGo > 0) {
322      unsigned writeCount;
323
324      writeCount = writeBytes
325        ? writeBytes(source, @selector(writeBytes:count:), str, toGo)
326        : [source writeBytes:str count:toGo];
327
328      if (writeCount == NGStreamError)
329        [[self->source lastException] raise];
330
331      toGo -= writeCount;
332      str  += writeCount;
333    }
334  }
335  NS_HANDLER {
336    if (buf != NULL) { free(buf); buf = NULL; };
337    [localException raise];
338  }
339  NS_ENDHANDLER;
340
341  if (buf) { free(buf); buf = NULL; }
342  return YES;
343}
344
345- (BOOL)flush {
346  if (flushBuffer)
347    return flushBuffer(self->source, @selector(flush));
348  else
349    return [self->source flush];
350}
351
352- (BOOL)writeNewline {
353  if (![self writeString:NGNewLineString]) return NO;
354  if (![self flush]) return NO;
355  return YES;
356}
357
358- (void) setEncoding: (NSStringEncoding) theEncoding
359{
360  self->encoding = theEncoding;
361}
362
363@end /* NGCTextStream */
364
365@implementation _NGCTextStreamLineEnumerator
366
367- (id)initWithTextStream:(NGCTextStream *)_stream {
368  self->stream = [_stream retain];
369  return self;
370}
371
372- (void)dealloc {
373  [self->stream release];
374  [super dealloc];
375}
376
377- (id)nextObject {
378  id result;
379
380  *(&result) = nil;
381
382  NS_DURING {
383    result = [self->stream readLineAsString];
384  }
385  NS_HANDLER {
386    if ([localException isKindOfClass:[NGEndOfStreamException class]])
387      result = nil;
388    else
389      [localException raise];
390  }
391  NS_ENDHANDLER;
392
393  return result;
394}
395
396@end /* _NGCTextStreamLineEnumerator */
397