NameFormatterBack.java

package space.sunqian.common.base.string;

import space.sunqian.annotations.Nonnull;
import space.sunqian.common.Check;
import space.sunqian.common.base.value.Span;
import space.sunqian.common.collect.ArrayKit;

final class NameFormatterBack {

    static @Nonnull NameFormatter camelCase(boolean upperFirst) {
        return new CamelCase(upperFirst);
    }

    static @Nonnull NameFormatter delimiterCase(
        @Nonnull CharSequence delimiter, @Nonnull NameFormatter.Appender appender
    ) throws IllegalArgumentException {
        return new DelimiterCase(delimiter, appender);
    }

    static @Nonnull NameFormatter fileNaming() {
        return new FileNaming();
    }

    static @Nonnull NameFormatter.Appender simpleAppender() {
        return SimpleAppender.INST;
    }

    private static final class CamelCase implements NameFormatter {

        private static final int LOWER = 1;
        private static final int UPPER = 2;
        private static final int NUM = 4;
        private static final int OTHER = 8;

        private final boolean upperFirst;

        private CamelCase(boolean upperFirst) {
            this.upperFirst = upperFirst;
        }

        @Override
        public @Nonnull Span @Nonnull [] tokenize(@Nonnull CharSequence name) {
            int len = name.length();
            if (len == 0) {
                return new Span[]{Span.empty()};
            }
            if (len == 1) {
                return new Span[]{Span.of(0, 1)};
            }
            int size = 1;
            int start = 0;
            int i = 1;
            int t1 = charType(name.charAt(0));
            while (i < name.length()) {
                int t2 = charType(name.charAt(i));
                if (t1 == t2) {
                    i++;
                    continue;
                }
                // Aa: check AAa and aAa
                if (t1 == UPPER && t2 == LOWER) {
                    if (start == i - 1) {
                        // Aa: one word
                        i++;
                        t1 = t2;
                        continue;
                    } else {
                        // AAa: split as A + Aa
                        start = i - 1;
                    }
                } else {
                    // others start from current char
                    start = i;
                }
                // others add one word
                size++;
                i++;
                t1 = t2;
            }
            Span[] spans = new Span[size];
            start = 0;
            i = 1;
            int wi = 0;
            t1 = charType(name.charAt(0));
            while (i < name.length()) {
                int t2 = charType(name.charAt(i));
                if (t1 == t2) {
                    i++;
                    continue;
                }
                // Aa: check AAa and aAa
                if (t1 == UPPER && t2 == LOWER) {
                    if (start == i - 1) {
                        // Aa: one word
                        i++;
                        t1 = t2;
                        continue;
                    } else {
                        // AAa: split as A + Aa
                        spans[wi++] = Span.of(start, i - 1);
                        start = i - 1;
                    }
                } else {
                    // others start from current char
                    spans[wi++] = Span.of(start, i);
                    start = i;
                }
                i++;
                t1 = t2;
            }
            spans[wi] = Span.of(start, name.length());
            return spans;
        }

        @Override
        public void format(
            @Nonnull CharSequence @Nonnull [] words, @Nonnull Appendable dst
        ) throws NameFormatException {
            if (words.length == 0) {
                return;
            }
            try {
                format0(words, dst);
            } catch (Exception e) {
                throw new NameFormatException(e);
            }
        }

        private void format0(
            @Nonnull CharSequence @Nonnull [] words, @Nonnull Appendable dst
        ) throws Exception {
            CharSequence first = words[0];
            appendFirstWord(dst, first, Span.of(0, first.length()));
            if (words.length > 1) {
                for (int i = 1; i < words.length; i++) {
                    appendWord(dst, words[i], Span.of(0, words[i].length()));
                }
            }
        }

        @Override
        public void format(
            @Nonnull CharSequence originalName,
            @Nonnull Span @Nonnull [] wordSpans,
            @Nonnull Appendable dst
        ) throws NameFormatException {
            if (wordSpans.length == 0) {
                return;
            }
            try {
                format0(originalName, wordSpans, dst);
            } catch (Exception e) {
                throw new NameFormatException(e);
            }
        }

        private void format0(
            @Nonnull CharSequence originalName,
            @Nonnull Span @Nonnull [] wordSpans,
            @Nonnull Appendable dst
        ) throws Exception {
            Span first = wordSpans[0];
            appendFirstWord(dst, originalName, first);
            if (wordSpans.length > 1) {
                for (int i = 1; i < wordSpans.length; i++) {
                    appendWord(dst, originalName, wordSpans[i]);
                }
            }
        }

        private void appendFirstWord(
            @Nonnull Appendable dst, @Nonnull CharSequence str, @Nonnull Span span
        ) throws Exception {
            if (span.isEmpty()) {
                return;
            }
            if (isAllUpper(str, span)) {
                dst.append(str, span.startIndex(), span.endIndex());
                return;
            }
            dst.append(upperFirst ?
                Character.toUpperCase(str.charAt(span.startIndex()))
                :
                Character.toLowerCase(str.charAt(span.startIndex()))
            );
            if (span.endIndex() - span.startIndex() == 1) {
                return;
            }
            dst.append(str, span.startIndex() + 1, span.endIndex());
        }

        private void appendWord(
            @Nonnull Appendable dst, @Nonnull CharSequence str, @Nonnull Span span
        ) throws Exception {
            if (span.isEmpty()) {
                return;
            }
            if (isAllUpper(str, span)) {
                dst.append(str, span.startIndex(), span.endIndex());
                return;
            }
            dst.append(Character.toUpperCase(str.charAt(span.startIndex())));
            if (span.endIndex() - span.startIndex() == 1) {
                return;
            }
            dst.append(str, span.startIndex() + 1, span.endIndex());
        }

        private boolean isAllUpper(@Nonnull CharSequence str, @Nonnull Span span) {
            if (span.length() < 2) {
                return false;
            }
            return charType(str.charAt(span.startIndex())) == UPPER
                && charType(str.charAt(span.startIndex() + 1)) == UPPER;
        }

        private int charType(char c) {
            if (c >= 'a' && c <= 'z') {
                return LOWER;
            }
            if (c >= 'A' && c <= 'Z') {
                return UPPER;
            }
            if (c >= '0' && c <= '9') {
                return NUM;
            }
            return OTHER;
        }
    }

    private static final class DelimiterCase implements NameFormatter {

        private final @Nonnull CharSequence delimiter;
        private final @Nonnull NameFormatter.Appender appender;

        private DelimiterCase(
            @Nonnull CharSequence delimiter, @Nonnull NameFormatter.Appender appender
        ) throws IllegalArgumentException {
            Check.checkArgument(delimiter.length() > 0, "The delimiter must not be empty.");
            this.delimiter = delimiter;
            this.appender = appender;
        }

        @Override
        public @Nonnull Span @Nonnull [] tokenize(@Nonnull CharSequence name) {
            if (name.length() < delimiter.length()) {
                return new Span[]{Span.of(0, name.length())};
            }
            int size = 1;
            int start = 0;
            while (start < name.length()) {
                int index = StringKit.indexOf(name, delimiter, start);
                if (index < 0) {
                    break;
                }
                size++;
                start = index + delimiter.length();
            }
            Span[] spans = new Span[size];
            int i = 0;
            start = 0;
            while (start < name.length()) {
                int index = StringKit.indexOf(name, delimiter, start);
                if (index < 0) {
                    break;
                }
                spans[i++] = Span.of(start, index);
                start = index + delimiter.length();
            }
            spans[i] = start < name.length() ? Span.of(start, name.length()) : Span.empty();
            return spans;
        }

        @Override
        public void format(
            @Nonnull CharSequence @Nonnull [] words, @Nonnull Appendable dst
        ) throws NameFormatException {
            if (words.length == 0) {
                return;
            }
            try {
                format0(words, dst);
            } catch (Exception e) {
                throw new NameFormatException(e);
            }
        }

        private void format0(
            @Nonnull CharSequence @Nonnull [] words, @Nonnull Appendable dst
        ) throws Exception {
            if (words.length == 1) {
                CharSequence first = words[0];
                dst.append(first);
                return;
            }
            dst.append(words[0]);
            for (int i = 1; i < words.length; i++) {
                dst.append(delimiter);
                dst.append(words[i]);
            }
        }

        @Override
        public void format(
            @Nonnull CharSequence originalName,
            @Nonnull Span @Nonnull [] wordSpans,
            @Nonnull Appendable dst
        ) throws NameFormatException {
            if (wordSpans.length == 0) {
                return;
            }
            try {
                format0(originalName, wordSpans, dst);
            } catch (Exception e) {
                throw new NameFormatException(e);
            }
        }

        private void format0(
            @Nonnull CharSequence originalName,
            @Nonnull Span @Nonnull [] wordSpans,
            @Nonnull Appendable dst
        ) throws Exception {
            if (wordSpans.length == 1) {
                Span first = wordSpans[0];
                appender.append(dst, originalName, first, 0);
                return;
            }
            appender.append(dst, originalName, wordSpans[0], 0);
            for (int i = 1; i < wordSpans.length; i++) {
                dst.append(delimiter);
                appender.append(dst, originalName, wordSpans[i], i);
            }
        }
    }

    private static final class FileNaming implements NameFormatter {

        private static final SimpleAppender appender = (SimpleAppender) simpleAppender();

        @Override
        public @Nonnull Span @Nonnull [] tokenize(@Nonnull CharSequence name) {
            int lastDot = StringKit.lastIndexOf(name, '.');
            if (lastDot < 0 || lastDot == name.length() - 1) {
                return new Span[]{Span.of(0, name.length())};
            }
            return ArrayKit.array(
                Span.of(0, lastDot),
                Span.of(lastDot + 1, name.length())
            );
        }

        @Override
        public void format(
            @Nonnull CharSequence @Nonnull [] words, @Nonnull Appendable dst
        ) throws NameFormatException {
            if (words.length == 0) {
                return;
            }
            try {
                format0(words, dst);
            } catch (Exception e) {
                throw new NameFormatException(e);
            }
        }

        private void format0(
            @Nonnull CharSequence @Nonnull [] words, @Nonnull Appendable dst
        ) throws Exception {
            if (words.length == 1) {
                CharSequence first = words[0];
                dst.append(first);
                return;
            }
            if (words.length == 2) {
                CharSequence prefix = words[0];
                CharSequence suffix = words[1];
                dst.append(prefix);
                dst.append('.');
                dst.append(suffix);
                return;
            }
            for (int i = 0; i < words.length - 1; i++) {
                dst.append(words[i]);
            }
            dst.append('.');
            CharSequence suffix = words[words.length - 1];
            dst.append(suffix);
        }

        @Override
        public void format(
            @Nonnull CharSequence originalName,
            @Nonnull Span @Nonnull [] wordSpans,
            @Nonnull Appendable dst
        ) throws NameFormatException {
            if (wordSpans.length == 0) {
                return;
            }
            try {
                format0(originalName, wordSpans, dst);
            } catch (Exception e) {
                throw new NameFormatException(e);
            }
        }

        private void format0(
            @Nonnull CharSequence originalName,
            @Nonnull Span @Nonnull [] wordSpans,
            @Nonnull Appendable dst
        ) throws Exception {
            if (wordSpans.length == 1) {
                Span first = wordSpans[0];
                appender.append(dst, originalName, first, 0);
                return;
            }
            if (wordSpans.length == 2) {
                Span prefix = wordSpans[0];
                Span suffix = wordSpans[1];
                appender.append(dst, originalName, prefix, 0);
                dst.append('.');
                appender.append(dst, originalName, suffix, 1);
                return;
            }
            for (int i = 0; i < wordSpans.length - 1; i++) {
                appender.append(dst, originalName, wordSpans[i], i);
            }
            dst.append('.');
            Span suffix = wordSpans[wordSpans.length - 1];
            appender.append(dst, originalName, suffix, wordSpans.length - 1);
        }
    }

    private enum SimpleAppender implements NameFormatter.Appender {

        INST;

        @Override
        public void append(
            @Nonnull Appendable dst, @Nonnull CharSequence originalName, @Nonnull Span span, int index
        ) throws Exception {
            dst.append(originalName, span.startIndex(), span.endIndex());
        }
    }

    private NameFormatterBack() {
    }
}