/** * Copyright (c) 2012 Cloudsmith Inc. and other contributors, as listed below. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloudsmith * */ package org.cloudsmith.geppetto.common; import java.util.List; import com.google.common.collect.Lists; /** * A CharSequence utilities class providing efficient "strings" consisting of a fixed space, * or specified character, a repeating sequence, concatenation, sub sequence and replacement methods for * String methods {@link #endsWith(CharSequence, String)}, {@link #indexOf(CharSequence, String, int)}, and a method * that splits a CharSequence into a List. * */ public class CharSequences { /** * A CharSequence consisting of a concatenation of two sequences * */ public static class Concatenation implements CharSequence { private CharSequence b; private CharSequence a; private int aLength; public Concatenation(CharSequence a, CharSequence b) { this.a = a; this.aLength = a.length(); this.b = b; } @Override public char charAt(int index) { if(index < aLength) return a.charAt(index); return b.charAt(index - aLength); } @Override public int length() { return a.length() + b.length(); } @Override public CharSequence subSequence(int start, int end) { return new SubSequence(this, start, end); } @Override public String toString() { return a.toString() + b.toString(); } } /** * An CharSequence consisting of a sequence of the same character. * */ public static class Fixed implements CharSequence { private int count; private char c; public Fixed(char c, int count) { this.c = c; this.count = count; } @Override public char charAt(int index) { return c; } @Override public int length() { return count; } @Override public CharSequence subSequence(int start, int end) { return new Fixed(c, end - start); } @Override public String toString() { StringBuilder builder = new StringBuilder(count); for(int i = 0; i < count; i++) builder.append(c); return builder.toString(); } } /** * An CharSequence consisting of a sequence of the same character. * */ public static class RepeatingString implements CharSequence { private int count; private String s; private int length; public RepeatingString(String s, int count) { this.s = s; this.count = count; this.length = s.length(); } @Override public char charAt(int index) { if(index >= count * length) throw new IndexOutOfBoundsException(); return s.charAt(index % length); } @Override public int length() { return count * length; } @Override public CharSequence subSequence(int start, int end) { int max = length(); if(start < 0 || end < 0 || start > end || end > max) throw new IndexOutOfBoundsException(); return new SubSequence(this, start, end); } @Override public String toString() { StringBuilder builder = new StringBuilder(count); for(int i = 0; i < count; i++) builder.append(s); return builder.toString(); } } /** * An CharSequence consisting of only space * */ public static class Spaces implements CharSequence { private static String space16 = " "; private int count; public Spaces(int count) { this.count = count; } @Override public char charAt(int index) { return ' '; } @Override public int length() { return count; } @Override public CharSequence subSequence(int start, int end) { return new Spaces(end - start); } @Override public String toString() { if(count < 17) return space16.substring(0, count); StringBuilder builder = new StringBuilder(count); for(int i = 0; i < count; i++) builder.append(' '); return builder.toString(); } } public static class SubSequence implements CharSequence { private CharSequence original; private int start; private int end; public SubSequence(CharSequence original, int start, int end) { this.original = original; this.start = start; this.end = end; } @Override public char charAt(int index) { return original.charAt(start + index); } @Override public int length() { return end - start; } @Override public CharSequence subSequence(int start, int end) { return new SubSequence(this, start, end); } @Override public String toString() { int limit = length(); StringBuilder builder = new StringBuilder(limit); for(int i = 0; i < limit; i++) builder.append(this.charAt(i)); return builder.toString(); } } private static final Spaces oneSpace = new Spaces(1); private static final Spaces zeroSpace = new Spaces(0); /** * Produces an efficient concatenation of two CharSequences in a safe way (they may be null). * No copies are being made (thus for excessive iteration of the result it is better to * make a copy of the result). * If a concatenation is not really needed (one is null or empty) the other sequence is returned. * * @param a * @param b * @return a CharSequence being a concatenation */ public static CharSequence concatenate(CharSequence a, CharSequence b) { if(a == null) a = ""; if(b == null) b = ""; if(a.length() == 0) return b; if(b.length() == 0) return a; return new Concatenation(a, b); } public static CharSequence empty() { return ""; } /** * Return the argument if is null or if its length > 0, else return <code>null</code>. * * @param s * The sequence to trim or <code>null</code> * @return The trimmed sequence or <code>null</code> */ public static CharSequence emptyToNull(CharSequence s) { if(s != null) { if(s.length() == 0) s = null; } return s; } public static boolean endsWith(CharSequence value, String end) { if(value instanceof String) return ((String) value).endsWith(end); int sz = value.length(); int szEnd = end.length(); if(value.length() < end.length()) return false; return CharSequences.equals(value.subSequence(sz - szEnd - 1, sz), end); } public static boolean equals(CharSequence o1, CharSequence o2) { if(o1 == o2) return true; if(o1 == null || o2 == null) return false; if(o1.length() != o2.length()) return false; int limit = o1.length(); for(int i = 0; i < limit; i++) if(o1.charAt(i) != o2.charAt(i)) return false; return true; } public static int indexOf(CharSequence value, String delimiter, int from) { if(value instanceof String) return ((String) value).indexOf(delimiter, from); else if(value instanceof StringBuilder) return ((StringBuilder) value).indexOf(delimiter, from); return value.toString().indexOf(delimiter, from); } public static int indexOfNonWhitespace(CharSequence value, int from) { for(int i = from; i < value.length(); i++) if(!Character.isWhitespace(value.charAt(i))) return i; return -1; } public static int indexOfWhitespace(CharSequence value, int from) { for(int i = from; i < value.length(); i++) if(Character.isWhitespace(value.charAt(i))) return i; return -1; } public static boolean isEmpty(CharSequence s) { return indexOfNonWhitespace(s, 0) < 0; } /** * Returns true if the comment consists of a repeated single character, or is empty. * * @param s * @return */ public static boolean isHomogeneous(CharSequence s) { if(s.length() < 1) return true; char c = s.charAt(0); int limit = s.length(); for(int i = 1; i < limit; i++) if(c != s.charAt(i)) return false; return true; } public static int lastIndexOf(CharSequence value, String delimiter, int from) { if(value instanceof String) return ((String) value).lastIndexOf(delimiter, from); else if(value instanceof StringBuilder) return ((StringBuilder) value).lastIndexOf(delimiter, from); return value.toString().lastIndexOf(delimiter, from); } public static int lastIndexOfNonWhitepace(CharSequence value, int from) { for(int i = from; i >= 0; i--) if(!Character.isWhitespace(value.charAt(i))) return i; return -1; } public static int lastIndexOfWhitespace(CharSequence value, int from) { for(int i = from; i >= 0; i--) if(Character.isWhitespace(value.charAt(i))) return i; return -1; } public static Spaces spaces(int count) { if(count < 0) return zeroSpace; if(count == 0) return zeroSpace; if(count == 1) return oneSpace; return new Spaces(count); } public static List<CharSequence> split(CharSequence value, String delimiter) { return split(value, delimiter, true); } /** * Split sequence on delimiter * * @param value * @param delimiter * @return */ public static List<CharSequence> split(CharSequence value, String delimiter, boolean includeTrailingEmpty) { List<CharSequence> result = Lists.newArrayList(); int lastIndex = 0; int index = indexOf(value, delimiter, lastIndex); while(index != -1) { result.add(value.subSequence(lastIndex, index)); lastIndex = index + delimiter.length(); index = indexOf(value, delimiter, lastIndex); } CharSequence last = value.subSequence(lastIndex, value.length()); if(includeTrailingEmpty || last.length() > 0) result.add(last); return result; } public static boolean startsWith(CharSequence value, String start) { if(value instanceof String) return ((String) value).startsWith(start); int szEnd = start.length(); if(value.length() < start.length()) return false; return CharSequences.equals(value.subSequence(0, szEnd), start); } public static CharSequence trim(CharSequence s) { int len = s.length(); return trim(s, len, len); } /** * Trims both left and right whitespace up to given limits. The maxLeftCount is only enforced if * right trimming stops before the maxLeftCountPosition. * * @param s * @param maxLeftCount * @param maxRightCount * @return a trimmed CharSequence */ public static CharSequence trim(CharSequence s, int maxLeftCount, int maxRightCount) { int limit = s.length(); int start = 0; int end = limit; while(maxLeftCount-- > 0 && start < limit - 1 && Character.isWhitespace(s.charAt(start))) start++; while(maxRightCount-- > 0 && end > 0 && Character.isWhitespace(s.charAt(end - 1))) end--; return s.subSequence(Math.min(start, end), end); } public static CharSequence trimLeft(CharSequence s) { return s.subSequence(indexOfNonWhitespace(s, 0), s.length()); } public static CharSequence trimRight(CharSequence s) { return s.subSequence(0, lastIndexOfNonWhitepace(s, s.length() - 1)); } /** * Trim both left and right whitespace. Return the result if the resulting * length > 0, else return <code>null</code>. * * @param s * The sequence to trim or <code>null</code> * @return The trimmed sequence or <code>null</code> */ public static CharSequence trimToNull(CharSequence s) { if(s != null) { int len = s.length(); if(len == 0) s = null; else { s = trim(s, len, len); if(s.length() == 0) s = null; } } return s; } }