001/** 002 * 003 * Copyright 2014-2023 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.util; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.List; 022 023public class LazyStringBuilder implements Appendable, CharSequence { 024 025 private final List<CharSequence> list; 026 027 private transient String cache; 028 private int cachedLength = -1; 029 030 private void invalidateCache() { 031 cache = null; 032 cachedLength = -1; 033 } 034 035 public LazyStringBuilder() { 036 list = new ArrayList<>(20); 037 } 038 039 public LazyStringBuilder append(LazyStringBuilder lsb) { 040 list.addAll(lsb.list); 041 invalidateCache(); 042 return this; 043 } 044 045 @Override 046 public LazyStringBuilder append(CharSequence csq) { 047 assert csq != null; 048 list.add(csq); 049 invalidateCache(); 050 return this; 051 } 052 053 @Override 054 public LazyStringBuilder append(CharSequence csq, int start, int end) { 055 CharSequence subsequence = csq.subSequence(start, end); 056 list.add(subsequence); 057 invalidateCache(); 058 return this; 059 } 060 061 @Override 062 public LazyStringBuilder append(char c) { 063 list.add(Character.toString(c)); 064 invalidateCache(); 065 return this; 066 } 067 068 @Override 069 public int length() { 070 if (cachedLength >= 0) { 071 return cachedLength; 072 } 073 074 int length = 0; 075 try { 076 for (CharSequence csq : list) { 077 length += csq.length(); 078 } 079 } 080 catch (NullPointerException npe) { 081 StringBuilder sb = safeToStringBuilder(); 082 throw new RuntimeException("The following LazyStringBuilder threw a NullPointerException: " + sb, npe); 083 } 084 085 cachedLength = length; 086 return length; 087 } 088 089 @Override 090 public char charAt(int index) { 091 if (cache != null) { 092 return cache.charAt(index); 093 } 094 for (CharSequence csq : list) { 095 if (index < csq.length()) { 096 return csq.charAt(index); 097 } else { 098 index -= csq.length(); 099 } 100 } 101 throw new IndexOutOfBoundsException(); 102 } 103 104 @Override 105 public CharSequence subSequence(int start, int end) { 106 return toString().subSequence(start, end); 107 } 108 109 @Override 110 public String toString() { 111 if (cache == null) { 112 StringBuilder sb = new StringBuilder(length()); 113 for (CharSequence csq : list) { 114 sb.append(csq); 115 } 116 cache = sb.toString(); 117 } 118 return cache; 119 } 120 121 public StringBuilder safeToStringBuilder() { 122 StringBuilder sb = new StringBuilder(); 123 for (CharSequence csq : list) { 124 sb.append(csq); 125 } 126 return sb; 127 } 128 129 /** 130 * Get the List of CharSequences representation of this instance. The list is unmodifiable. If 131 * the resulting String was already cached, a list with a single String entry will be returned. 132 * 133 * @return a List of CharSequences representing this instance. 134 */ 135 public List<CharSequence> getAsList() { 136 if (cache != null) { 137 return Collections.singletonList((CharSequence) cache); 138 } 139 return Collections.unmodifiableList(list); 140 } 141}