view graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/core/RubyString.java @ 13514:0fbee3eb71f0

Ruby: import project.
author Chris Seaton <chris.seaton@oracle.com>
date Mon, 06 Jan 2014 17:12:09 +0000
parents
children
line wrap: on
line source

/*
 * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
 * code is released under a tri EPL/GPL/LGPL license. You can use it,
 * redistribute it and/or modify it under the terms of the:
 *
 * Eclipse Public License version 1.0
 * GNU General Public License version 2
 * GNU Lesser General Public License version 2.1
 */
package com.oracle.truffle.ruby.runtime.core;

import java.math.*;
import java.nio.*;
import java.nio.charset.*;
import java.util.*;
import java.util.regex.*;

import com.oracle.truffle.ruby.runtime.*;
import com.oracle.truffle.ruby.runtime.core.array.*;
import com.oracle.truffle.ruby.runtime.core.range.*;
import com.oracle.truffle.ruby.runtime.objects.*;

/**
 * Represents the Ruby {@code String} class.
 */
public class RubyString extends RubyObject {

    /**
     * The class from which we create the object that is {@code String}. A subclass of
     * {@link RubyClass} so that we can override {@link #newInstance} and allocate a
     * {@link RubyString} rather than a normal {@link RubyBasicObject}.
     */
    public static class RubyStringClass extends RubyClass {

        public RubyStringClass(RubyClass objectClass) {
            super(null, objectClass, "String");
        }

        @Override
        public RubyBasicObject newInstance() {
            return new RubyString(getContext().getCoreLibrary().getStringClass(), "");
        }

    }

    private boolean fromJavaString;

    private Charset encoding;
    private byte[] bytes;

    private String cachedStringValue;

    /**
     * Construct a string from a Java {@link String}, lazily converting to bytes as needed.
     */
    public RubyString(RubyClass stringClass, String value) {
        super(stringClass);
        fromJavaString = true;
        encoding = null;
        bytes = null;
        cachedStringValue = value;
    }

    /**
     * Construct a string from bytes representing characters in an encoding, lazily converting to a
     * Java {@link String} as needed.
     */
    public RubyString(RubyClass stringClass, Charset encoding, byte[] bytes) {
        super(stringClass);
        fromJavaString = false;
        this.encoding = encoding;
        this.bytes = bytes;
        cachedStringValue = null;
    }

    public RubyString(RubyString copyOf) {
        super(copyOf.getRubyClass().getContext().getCoreLibrary().getStringClass());
        fromJavaString = copyOf.fromJavaString;
        encoding = copyOf.encoding;

        if (copyOf.bytes != null) {
            bytes = Arrays.copyOf(copyOf.bytes, copyOf.bytes.length);
        } else {
            bytes = null;
        }

        cachedStringValue = copyOf.cachedStringValue;
    }

    public boolean isFromJavaString() {
        return fromJavaString;
    }

    public byte[] getBytes() {
        return bytes;
    }

    public void replace(String value) {
        fromJavaString = true;
        encoding = null;
        bytes = null;
        cachedStringValue = value;
    }

    @Override
    public String toString() {
        if (cachedStringValue == null) {
            cachedStringValue = encoding.decode(ByteBuffer.wrap(bytes)).toString();
        }

        return cachedStringValue;
    }

    @Override
    public boolean equals(Object other) {
        if (other == null) {
            return false;
        }

        // If the other value is a Java string, use our Java string representation to compare

        if (other instanceof String) {
            return toString().equals(other);
        }

        if (other instanceof RubyString) {
            final RubyString otherString = (RubyString) other;

            // If we both came from Java strings, use them to compare

            if (fromJavaString && otherString.fromJavaString) {
                return toString().equals(other.toString());
            }

            // If we both have the same encoding, compare bytes

            if (encoding == otherString.encoding) {
                return Arrays.equals(bytes, otherString.bytes);
            }

            // If we don't have the same encoding, we need some more advanced logic

            throw new UnsupportedOperationException("Can't compare strings in different encodings yet");
        }

        return false;
    }

    @Override
    public int hashCode() {
        return toString().hashCode();
    }

    public static Object getIndex(RubyContext context, String string, Object[] args) {
        if (args.length == 1) {
            final Object index = args[0];

            if (index instanceof Integer) {
                final int stringLength = string.length();
                final int normalisedIndex = ArrayUtilities.normaliseIndex(stringLength, (int) index);

                return context.makeString(string.charAt(normalisedIndex));
            } else if (index instanceof FixnumRange) {
                final FixnumRange range = (FixnumRange) index;

                final int stringLength = string.length();

                if (range.doesExcludeEnd()) {
                    final int begin = ArrayUtilities.normaliseIndex(stringLength, range.getBegin());
                    final int exclusiveEnd = ArrayUtilities.normaliseExclusiveIndex(stringLength, range.getExclusiveEnd());
                    return context.makeString(string.substring(begin, exclusiveEnd));
                } else {
                    final int begin = ArrayUtilities.normaliseIndex(stringLength, range.getBegin());
                    final int inclusiveEnd = ArrayUtilities.normaliseIndex(stringLength, range.getInclusiveEnd());
                    return context.makeString(string.substring(begin, inclusiveEnd + 1));
                }
            } else {
                throw new UnsupportedOperationException("Don't know how to index a string with " + index.getClass());
            }
        } else {
            final int rangeStart = (int) args[0];
            int rangeLength = (int) args[1];

            if (rangeLength > string.length() - rangeStart) {
                rangeLength = string.length() - rangeStart;
            }

            if (rangeStart > string.length()) {
                return NilPlaceholder.INSTANCE;
            }

            return context.makeString(string.substring(rangeStart, rangeStart + rangeLength));
        }
    }

    @Override
    public Object dup() {
        return new RubyString(this);
    }

    public void concat(RubyString other) {
        if (fromJavaString && other.fromJavaString) {
            cachedStringValue += other.cachedStringValue;
            encoding = null;
            bytes = null;
        } else {
            throw new UnsupportedOperationException("Don't know how to append strings with encodings");
        }
    }

    public static String ljust(String string, int length, String padding) {
        final StringBuilder builder = new StringBuilder();

        builder.append(string);

        int n = 0;

        while (builder.length() < length) {
            builder.append(padding.charAt(n));

            n++;

            if (n == padding.length()) {
                n = 0;
            }
        }

        return builder.toString();
    }

    public static String rjust(String string, int length, String padding) {
        final StringBuilder builder = new StringBuilder();

        int n = 0;

        while (builder.length() + string.length() < length) {
            builder.append(padding.charAt(n));

            n++;

            if (n == padding.length()) {
                n = 0;
            }
        }

        builder.append(string);

        return builder.toString();
    }

    public static RubyArray scan(RubyContext context, String string, Pattern pattern) {
        final Matcher matcher = pattern.matcher(string);

        final RubyArray results = new RubyArray(context.getCoreLibrary().getArrayClass());

        while (matcher.find()) {
            if (matcher.groupCount() == 0) {
                results.push(context.makeString(matcher.group(0)));
            } else {
                final RubyArray subResults = new RubyArray(context.getCoreLibrary().getArrayClass());

                for (int n = 1; n < matcher.groupCount() + 1; n++) {
                    subResults.push(context.makeString(matcher.group(n)));
                }

                results.push(subResults);
            }
        }

        return results;
    }

    public Object toInteger() {
        if (toString().length() == 0) {
            return 0;
        }

        try {
            final int value = Integer.parseInt(toString());

            if (value >= RubyFixnum.MIN_VALUE && value <= RubyFixnum.MAX_VALUE) {
                return value;
            } else {
                return BigInteger.valueOf(value);
            }
        } catch (NumberFormatException e) {
            return new BigInteger(toString());
        }
    }

}