001/** 002 * 003 * Copyright 2014-2017 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; 022import java.util.logging.Level; 023import java.util.logging.Logger; 024 025public class LazyStringBuilder implements Appendable, CharSequence { 026 027 private static final Logger LOGGER = Logger.getLogger(LazyStringBuilder.class.getName()); 028 029 private final List<CharSequence> list; 030 031 private String cache; 032 033 private void invalidateCache() { 034 cache = null; 035 } 036 037 public LazyStringBuilder() { 038 list = new ArrayList<>(20); 039 } 040 041 public LazyStringBuilder append(LazyStringBuilder lsb) { 042 list.addAll(lsb.list); 043 invalidateCache(); 044 return this; 045 } 046 047 @Override 048 public LazyStringBuilder append(CharSequence csq) { 049 assert csq != null; 050 list.add(csq); 051 invalidateCache(); 052 return this; 053 } 054 055 @Override 056 public LazyStringBuilder append(CharSequence csq, int start, int end) { 057 CharSequence subsequence = csq.subSequence(start, end); 058 list.add(subsequence); 059 invalidateCache(); 060 return this; 061 } 062 063 @Override 064 public LazyStringBuilder append(char c) { 065 list.add(Character.toString(c)); 066 invalidateCache(); 067 return this; 068 } 069 070 @Override 071 public int length() { 072 if (cache != null) { 073 return cache.length(); 074 } 075 int length = 0; 076 try { 077 for (CharSequence csq : list) { 078 length += csq.length(); 079 } 080 } 081 catch (NullPointerException npe) { 082 StringBuilder sb = safeToStringBuilder(); 083 LOGGER.log(Level.SEVERE, "The following LazyStringBuilder threw a NullPointerException: " + sb, npe); 084 throw npe; 085 } 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}