001: /***** BEGIN LICENSE BLOCK *****
002: * Version: CPL 1.0/GPL 2.0/LGPL 2.1
003: *
004: * The contents of this file are subject to the Common Public
005: * License Version 1.0 (the "License"); you may not use this file
006: * except in compliance with the License. You may obtain a copy of
007: * the License at http://www.eclipse.org/legal/cpl-v10.html
008: *
009: * Software distributed under the License is distributed on an "AS
010: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
011: * implied. See the License for the specific language governing
012: * rights and limitations under the License.
013: *
014: * Copyright (C) 2006 Thomas E Enebo <enebo@acm.org>
015: * Copyright (C) 2007 Koichiro Ohba <koichiro@meadowy.org>
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either of the GNU General Public License Version 2 or later (the "GPL"),
019: * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the CPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the CPL, the GPL or the LGPL.
028: ***** END LICENSE BLOCK *****/package org.jruby;
029:
030: import java.nio.ByteBuffer;
031: import java.nio.CharBuffer;
032: import java.nio.charset.CharacterCodingException;
033: import java.nio.charset.Charset;
034: import java.nio.charset.CharsetDecoder;
035: import java.nio.charset.CharsetEncoder;
036: import java.nio.charset.CodingErrorAction;
037: import java.nio.charset.IllegalCharsetNameException;
038: import java.nio.charset.MalformedInputException;
039: import java.nio.charset.UnmappableCharacterException;
040: import java.nio.charset.UnsupportedCharsetException;
041: import org.jruby.runtime.Arity;
042:
043: import org.jruby.runtime.Block;
044: import org.jruby.runtime.CallbackFactory;
045: import org.jruby.runtime.ObjectAllocator;
046: import org.jruby.runtime.builtin.IRubyObject;
047:
048: import org.jruby.util.ByteList;
049:
050: public class RubyIconv extends RubyObject {
051: //static private final String TRANSLIT = "//translit";
052: static private final String IGNORE = "//ignore";
053:
054: private CharsetDecoder fromEncoding;
055: private CharsetEncoder toEncoding;
056:
057: public RubyIconv(Ruby runtime, RubyClass type) {
058: super (runtime, type);
059: }
060:
061: private static ObjectAllocator ICONV_ALLOCATOR = new ObjectAllocator() {
062: public IRubyObject allocate(Ruby runtime, RubyClass klass) {
063: return new RubyIconv(runtime, klass);
064: }
065: };
066:
067: public static void createIconv(Ruby runtime) {
068: RubyClass iconvClass = runtime.defineClass("Iconv", runtime
069: .getObject(), ICONV_ALLOCATOR);
070: CallbackFactory callbackFactory = runtime
071: .callbackFactory(RubyIconv.class);
072:
073: iconvClass.getMetaClass().defineFastMethod("iconv",
074: callbackFactory.getOptSingletonMethod("iconv"));
075: iconvClass.getMetaClass().defineFastMethod("conv",
076: callbackFactory.getOptSingletonMethod("conv"));
077: iconvClass.getMetaClass().defineMethod(
078: "open",
079: callbackFactory.getSingletonMethod("open",
080: RubyKernel.IRUBY_OBJECT,
081: RubyKernel.IRUBY_OBJECT));
082:
083: iconvClass.defineMethod("initialize", callbackFactory
084: .getOptMethod("initialize"));
085: iconvClass.defineFastMethod("iconv", callbackFactory
086: .getFastOptMethod("iconv"));
087: iconvClass.defineFastMethod("close", callbackFactory
088: .getFastMethod("close"));
089:
090: RubyModule failure = iconvClass.defineModuleUnder("Failure");
091: CallbackFactory failureCallbackFactory = runtime
092: .callbackFactory(RubyFailure.class);
093: RubyClass argumentError = runtime.getClass("ArgumentError");
094:
095: String[] iconvErrors = { "IllegalSequence", "InvalidCharacter",
096: "InvalidEncoding", "OutOfRange", "BrokenLibrary" };
097:
098: for (int i = 0; i < iconvErrors.length; i++) {
099: RubyClass subClass = iconvClass.defineClassUnder(
100: iconvErrors[i], argumentError,
101: RubyFailure.ICONV_FAILURE_ALLOCATOR);
102: subClass.defineMethod("initialize", failureCallbackFactory
103: .getOptMethod("initialize"));
104: subClass.defineFastMethod("success", failureCallbackFactory
105: .getFastMethod("success"));
106: subClass.defineFastMethod("failed", failureCallbackFactory
107: .getFastMethod("failed"));
108: subClass.defineFastMethod("inspect", failureCallbackFactory
109: .getFastMethod("inspect"));
110: subClass.includeModule(failure);
111: }
112: }
113:
114: public static class RubyFailure extends RubyException {
115: private RubyString success;
116: private RubyString failed;
117:
118: public static RubyFailure newInstance(Ruby runtime,
119: RubyClass excptnClass, String msg) {
120: return new RubyFailure(runtime, excptnClass, msg);
121: }
122:
123: protected static ObjectAllocator ICONV_FAILURE_ALLOCATOR = new ObjectAllocator() {
124: public IRubyObject allocate(Ruby runtime, RubyClass klass) {
125: return new RubyFailure(runtime, klass);
126: }
127: };
128:
129: protected RubyFailure(Ruby runtime, RubyClass rubyClass) {
130: this (runtime, rubyClass, null);
131: }
132:
133: public RubyFailure(Ruby runtime, RubyClass rubyClass,
134: String message) {
135: super (runtime, rubyClass, message);
136: }
137:
138: public IRubyObject initialize(IRubyObject[] args, Block block) {
139: Arity.checkArgumentCount(getRuntime(), args, 3, 3);
140: super .initialize(args, block);
141: success = (RubyString) args[1];
142: failed = (RubyString) args[2];
143:
144: return this ;
145: }
146:
147: public IRubyObject success() {
148: return success;
149: }
150:
151: public IRubyObject failed() {
152: return failed;
153: }
154:
155: public IRubyObject inspect() {
156: RubyModule rubyClass = getMetaClass();
157: StringBuffer buffer = new StringBuffer("#<");
158: buffer.append(rubyClass.getName()).append(": ").append(
159: success.inspect().toString());
160: buffer.append(", ").append(failed.inspect().toString())
161: .append(">");
162:
163: return getRuntime().newString(buffer.toString());
164: }
165: }
166:
167: private static String getCharset(String encoding) {
168: int index = encoding.indexOf("//");
169: if (index == -1)
170: return encoding;
171: return encoding.substring(0, index);
172: }
173:
174: /* Currently dead code, but useful when we figure out how to actually perform translit.
175: private static boolean isTranslit(String encoding) {
176: return encoding.toLowerCase().indexOf(TRANSLIT) != -1 ? true : false;
177: }*/
178:
179: private static boolean isIgnore(String encoding) {
180: return encoding.toLowerCase().indexOf(IGNORE) != -1 ? true
181: : false;
182: }
183:
184: public static IRubyObject open(IRubyObject recv, IRubyObject to,
185: IRubyObject from, Block block) {
186: Ruby runtime = recv.getRuntime();
187: RubyIconv iconv = (RubyIconv) runtime.getClass("Iconv")
188: .newInstance(new IRubyObject[] { to, from },
189: Block.NULL_BLOCK);
190: if (!block.isGiven())
191: return iconv;
192:
193: IRubyObject result = runtime.getNil();
194: try {
195: result = block.yield(recv.getRuntime().getCurrentContext(),
196: iconv);
197: } finally {
198: iconv.close();
199: }
200:
201: return result;
202: }
203:
204: public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) {
205: Arity.checkArgumentCount(getRuntime(), args, 2, 2);
206: Ruby runtime = getRuntime();
207: if (!args[0].respondsTo("to_str")) {
208: throw runtime.newTypeError("can't convert "
209: + args[0].getMetaClass() + " into String");
210: }
211: if (!args[1].respondsTo("to_str")) {
212: throw runtime.newTypeError("can't convert "
213: + args[1].getMetaClass() + " into String");
214: }
215:
216: String to = args[0].convertToString().toString();
217: String from = args[1].convertToString().toString();
218:
219: try {
220:
221: fromEncoding = Charset.forName(getCharset(from))
222: .newDecoder();
223: toEncoding = Charset.forName(getCharset(to)).newEncoder();
224:
225: if (!isIgnore(from))
226: fromEncoding
227: .onUnmappableCharacter(CodingErrorAction.REPORT);
228: if (!isIgnore(to))
229: toEncoding
230: .onUnmappableCharacter(CodingErrorAction.REPORT);
231: } catch (IllegalCharsetNameException e) {
232: throw runtime.newArgumentError("invalid encoding");
233: } catch (UnsupportedCharsetException e) {
234: throw runtime.newArgumentError("invalid encoding");
235: } catch (Exception e) {
236: throw runtime.newSystemCallError(e.toString());
237: }
238:
239: return this ;
240: }
241:
242: public IRubyObject close() {
243: toEncoding = null;
244: fromEncoding = null;
245: return getRuntime().newString("");
246: }
247:
248: public IRubyObject iconv(IRubyObject[] args) {
249: Ruby runtime = getRuntime();
250: args = Arity.scanArgs(runtime, args, 1, 2);
251: int start = 0;
252: int length = -1;
253:
254: if (args[0].isNil()) {
255: fromEncoding.reset();
256: toEncoding.reset();
257: return runtime.newString("");
258: }
259: if (!args[0].respondsTo("to_str")) {
260: throw runtime.newTypeError("can't convert "
261: + args[0].getMetaClass() + " into String");
262: }
263: if (!args[1].isNil())
264: start = RubyNumeric.fix2int(args[1]);
265: if (!args[2].isNil())
266: length = RubyNumeric.fix2int(args[2]);
267:
268: IRubyObject result = _iconv(args[0].convertToString(), start,
269: length);
270: return result;
271: }
272:
273: // FIXME: We are assuming that original string will be raw bytes. If -Ku is provided
274: // this will not be true, but that is ok for now. Deal with that when someone needs it.
275: private IRubyObject _iconv(RubyString str, int start, int length) {
276: ByteList bytes = str.getByteList();
277:
278: if (length < 0)
279: length = bytes.length() - start;
280:
281: ByteBuffer buf = ByteBuffer.wrap(bytes.unsafeBytes(), start,
282: length);
283:
284: try {
285: CharBuffer cbuf = fromEncoding.decode(buf);
286: buf = toEncoding.encode(cbuf);
287: } catch (MalformedInputException e) {
288: } catch (UnmappableCharacterException e) {
289: } catch (CharacterCodingException e) {
290: throw getRuntime().newInvalidEncoding("invalid sequence");
291: } catch (IllegalStateException e) {
292: }
293: byte[] arr = buf.array();
294:
295: return getRuntime()
296: .newString(new ByteList(arr, 0, buf.limit()));
297: }
298:
299: public static IRubyObject iconv(IRubyObject recv,
300: IRubyObject[] args, Block unusedBlock) {
301: return convertWithArgs(recv, args, "iconv");
302: }
303:
304: public static IRubyObject conv(IRubyObject recv,
305: IRubyObject[] args, Block unusedBlock) {
306: return convertWithArgs(recv, args, "conv").join(
307: recv.getRuntime().newString(""));
308: }
309:
310: public static RubyArray convertWithArgs(IRubyObject recv,
311: IRubyObject[] args, String function) {
312: Arity.checkArgumentCount(recv.getRuntime(), args, 3, -1);
313:
314: String fromEncoding = args[1].convertToString().toString();
315: String toEncoding = args[0].convertToString().toString();
316: RubyArray array = recv.getRuntime().newArray();
317:
318: for (int i = 2; i < args.length; i++) {
319: array.append(convert2(fromEncoding, toEncoding, args[i]
320: .convertToString()));
321: }
322:
323: return array;
324: }
325:
326: /*
327: private static IRubyObject convert(String fromEncoding, String toEncoding, RubyString original)
328: throws UnsupportedEncodingException {
329: // Get all bytes from PLAIN string pretend they are not encoded in any way.
330: byte[] string = original.getBytes();
331: // Now create a string pretending it is from fromEncoding
332: string = new String(string, fromEncoding).getBytes(toEncoding);
333: // Finally recode back to PLAIN
334: return RubyString.newString(original.getRuntime(), string);
335: }
336: */
337:
338: // FIXME: We are assuming that original string will be raw bytes. If -Ku is provided
339: // this will not be true, but that is ok for now. Deal with that when someone needs it.
340: private static IRubyObject convert2(String fromEncoding,
341: String toEncoding, RubyString original) {
342: try {
343: // Get all bytes from string and pretend they are not encoded in any way.
344: ByteList bytes = original.getByteList();
345: ByteBuffer buf = ByteBuffer.wrap(bytes.unsafeBytes(), bytes
346: .begin(), bytes.length());
347:
348: CharsetDecoder decoder = Charset.forName(
349: getCharset(fromEncoding)).newDecoder();
350:
351: if (!isIgnore(fromEncoding))
352: decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
353:
354: CharBuffer cbuf = decoder.decode(buf);
355: CharsetEncoder encoder = Charset.forName(
356: getCharset(toEncoding)).newEncoder();
357:
358: if (!isIgnore(toEncoding))
359: encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
360:
361: buf = encoder.encode(cbuf);
362: byte[] arr = buf.array();
363: return RubyString.newString(original.getRuntime(),
364: new ByteList(arr, 0, buf.limit()));
365: } catch (UnsupportedCharsetException e) {
366: throw original.getRuntime().newInvalidEncoding(
367: "invalid encoding");
368: } catch (UnmappableCharacterException e) {
369: } catch (CharacterCodingException e) {
370: }
371: return original.getRuntime().getNil();
372: }
373: }
|