Modified: pivot/trunk/core/src/org/apache/pivot/serialization/MacroReader.java URL: http://svn.apache.org/viewvc/pivot/trunk/core/src/org/apache/pivot/serialization/MacroReader.java?rev=1913470&r1=1913469&r2=1913470&view=diff ============================================================================== --- pivot/trunk/core/src/org/apache/pivot/serialization/MacroReader.java (original) +++ pivot/trunk/core/src/org/apache/pivot/serialization/MacroReader.java Tue Oct 31 19:15:47 2023 @@ -1,249 +1,249 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.pivot.serialization; - -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayDeque; -import java.util.HashMap; -import java.util.Map; -import java.util.Queue; - - -/** - * This is a {@link Reader} that can be instantiated inline with any other - * {@code Reader} to provide macro capabilities. - * <p> We recognize <code>#define <i>NAME value</i></code> as definitions - * as well as <code>#undef <i>NAME</i></code> to remove a previous definition. - * <p> The macro name must correspond to the Unicode naming conventions (see - * {@link Character#isUnicodeIdentifierStart} and {@link Character#isUnicodeIdentifierPart}). - * <p> Macro substitutions are recognized as <code>${<i>NAME</i>}</code> anywhere - * in the underlying stream. Nested macros are supported, and are expanded at the - * point of definition, if defined, or at the point of expansion if defined later. - */ -public final class MacroReader extends Reader { - /** The input reader we are mapping. */ - private Reader in; - - /** The map of our defined variables and their values. */ - private Map<String, String> variableMap = new HashMap<>(); - - /** The lookahead queue, set either by one-character lookahead (such as - * while recognizing "$NAME") or from macro expansion. - */ - private Queue<Integer> lookaheadQueue = new ArrayDeque<>(); - - /** The previous character read. */ - private int lastCh = -1; - - /** - * Construct a new macro reader on top of the given reader. - * @param reader The input reader we are going to wrap. - */ - public MacroReader(final Reader reader) { - this.in = reader; - } - - @Override - public void close() throws IOException { - in.close(); - } - - /** - * Add the given character to the lookahead queue for reprocessing. - * @param ch The character to queue. - */ - private void queue(final int ch) { - if (ch != -1) { - lookaheadQueue.add(ch); - } - } - - /** - * Add all the characters of the given character sequence to the - * lookahead queue for reprocessing. - * @param seq The character sequence to be queued up again. - */ - private void queue(final CharSequence seq) { - for (int i = 0; i < seq.length(); i++) { - lookaheadQueue.add((int) seq.charAt(i)); - } - } - - /** - * Parse out the next word in the stream (according to Unicode - * Identifier semantics) as the macro name, skipping leading whitespace. - * @return The next parsed "word" string. - * @throws IOException for errors reading the stream. - */ - private String getNextWord() throws IOException { - StringBuilder buf = new StringBuilder(); - int ch; - do { - ch = getNextChar(true); - } while (ch != -1 && Character.isWhitespace(ch)); - - if (ch != -1) { - buf.append((char) ch); - while ((ch = getNextChar(true)) != -1 - && ((buf.length() == 0 && Character.isUnicodeIdentifierStart(ch)) - || (buf.length() > 0 && Character.isUnicodeIdentifierPart(ch)))) { - buf.append((char) ch); - } - - // Re-queue the character that terminated the word - queue(ch); - } - return buf.toString(); - } - - /** - * Get the next character in the input stream, either from the - * {@link #lookaheadQueue} if anything is queued, or by reading - * from the underlying {@link Reader}. - * <p> This is the heart of the processing that handles both - * macro definition and expansion. - * @param handleMacros set to {@code false} only when - * invoking this method recursively - * to ignore unknown macro commands - * or undefined macros - * @return The next character in the stream. - * @throws IOException if there are errors reading the stream. - */ - private int getNextChar(final boolean handleMacros) throws IOException { - int ret = -1; - Integer queuedChar = lookaheadQueue.poll(); - if (queuedChar != null) { - ret = queuedChar.intValue(); - } else { - ret = in.read(); - } - - if (handleMacros) { - int ch; - - // Check for macro define or undefine (starting with "#" - // at the beginning of a line) (unless we're recursing to - // skip an unknown declaration keyword). - if (ret == '#' && lastCh == '\n') { - String keyword = getNextWord(); - - if (keyword.equalsIgnoreCase("undef")) { - String name = getNextWord(); - - do { - ch = getNextChar(false); - } while (ch != -1 && ch != '\n'); - - variableMap.remove(name); - - return getNextChar(true); - } else if (!keyword.equalsIgnoreCase("define")) { - // Basically ignore any commands we don't understand - // by simply queueing the text back to be read again - // but with the flag set to ignore this command (so - // we don't get into infinite recursion!) - ch = getNextChar(false); - queue(ret); - queue(keyword); - queue(ch); - - return getNextChar(false); - } - - // Define a macro - String name = getNextWord(); - StringBuilder buf = new StringBuilder(); - - // Skip whitespace after the macro name - do { - ch = getNextChar(true); - } while (ch != -1 && Character.isWhitespace(ch) && ch != '\\' && ch != '\n'); - - queue(ch); - - do { - while ((ch = getNextChar(true)) != -1 && ch != '\\' && ch != '\n') { - buf.append((char) ch); - } - - // Check for line continuation character - if (ch == '\\') { - int next = getNextChar(true); - if (next == '\n') { - buf.append((char) next); - } else { - buf.append((char) ch); - buf.append((char) next); - } - } - } while (ch != -1 && ch != '\n'); - - variableMap.put(name, buf.toString()); - - return getNextChar(true); - } else if (ret == '$') { - // Check for macro expansion - // Note: this allows for nested expansion - int next = getNextChar(true); - if (next == '{') { - StringBuilder buf = new StringBuilder(); - - while ((ch = getNextChar(true)) != -1 && ch != '}') { - buf.append((char) ch); - } - - String expansion = variableMap.get(buf.toString()); - if (expansion == null) { - // If the macro is undefined, then we put back the - // original text to be read again, but without expansion - queue(ret); - queue(next); - queue(buf); - queue(ch); - - ret = getNextChar(false); - } else { - queue(expansion); - - // By allowing expansion here, we get recursive expansion - ret = getNextChar(true); - } - } else { - queue(next); - } - } - } - - return (lastCh = ret); - } - - @Override - public int read(final char[] cbuf, final int off, final int len) throws IOException { - int read = -1; - for (int i = 0; i < len; i++) { - int ch = getNextChar(true); - if (ch == -1) { - break; - } - read = i; - cbuf[off + i] = (char) ch; - } - return (read == -1) ? read : read + 1; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.pivot.serialization; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; + + +/** + * This is a {@link Reader} that can be instantiated inline with any other + * {@code Reader} to provide macro capabilities. + * <p> We recognize <code>#define <i>NAME value</i></code> as definitions + * as well as <code>#undef <i>NAME</i></code> to remove a previous definition. + * <p> The macro name must correspond to the Unicode naming conventions (see + * {@link Character#isUnicodeIdentifierStart} and {@link Character#isUnicodeIdentifierPart}). + * <p> Macro substitutions are recognized as <code>${<i>NAME</i>}</code> anywhere + * in the underlying stream. Nested macros are supported, and are expanded at the + * point of definition, if defined, or at the point of expansion if defined later. + */ +public final class MacroReader extends Reader { + /** The input reader we are mapping. */ + private Reader in; + + /** The map of our defined variables and their values. */ + private Map<String, String> variableMap = new HashMap<>(); + + /** The lookahead queue, set either by one-character lookahead (such as + * while recognizing "$NAME") or from macro expansion. + */ + private Queue<Integer> lookaheadQueue = new ArrayDeque<>(); + + /** The previous character read. */ + private int lastCh = -1; + + /** + * Construct a new macro reader on top of the given reader. + * @param reader The input reader we are going to wrap. + */ + public MacroReader(final Reader reader) { + this.in = reader; + } + + @Override + public void close() throws IOException { + in.close(); + } + + /** + * Add the given character to the lookahead queue for reprocessing. + * @param ch The character to queue. + */ + private void queue(final int ch) { + if (ch != -1) { + lookaheadQueue.add(ch); + } + } + + /** + * Add all the characters of the given character sequence to the + * lookahead queue for reprocessing. + * @param seq The character sequence to be queued up again. + */ + private void queue(final CharSequence seq) { + for (int i = 0; i < seq.length(); i++) { + lookaheadQueue.add((int) seq.charAt(i)); + } + } + + /** + * Parse out the next word in the stream (according to Unicode + * Identifier semantics) as the macro name, skipping leading whitespace. + * @return The next parsed "word" string. + * @throws IOException for errors reading the stream. + */ + private String getNextWord() throws IOException { + StringBuilder buf = new StringBuilder(); + int ch; + do { + ch = getNextChar(true); + } while (ch != -1 && Character.isWhitespace(ch)); + + if (ch != -1) { + buf.append((char) ch); + while ((ch = getNextChar(true)) != -1 + && ((buf.length() == 0 && Character.isUnicodeIdentifierStart(ch)) + || (buf.length() > 0 && Character.isUnicodeIdentifierPart(ch)))) { + buf.append((char) ch); + } + + // Re-queue the character that terminated the word + queue(ch); + } + return buf.toString(); + } + + /** + * Get the next character in the input stream, either from the + * {@link #lookaheadQueue} if anything is queued, or by reading + * from the underlying {@link Reader}. + * <p> This is the heart of the processing that handles both + * macro definition and expansion. + * @param handleMacros set to {@code false} only when + * invoking this method recursively + * to ignore unknown macro commands + * or undefined macros + * @return The next character in the stream. + * @throws IOException if there are errors reading the stream. + */ + private int getNextChar(final boolean handleMacros) throws IOException { + int ret = -1; + Integer queuedChar = lookaheadQueue.poll(); + if (queuedChar != null) { + ret = queuedChar.intValue(); + } else { + ret = in.read(); + } + + if (handleMacros) { + int ch; + + // Check for macro define or undefine (starting with "#" + // at the beginning of a line) (unless we're recursing to + // skip an unknown declaration keyword). + if (ret == '#' && lastCh == '\n') { + String keyword = getNextWord(); + + if (keyword.equalsIgnoreCase("undef")) { + String name = getNextWord(); + + do { + ch = getNextChar(false); + } while (ch != -1 && ch != '\n'); + + variableMap.remove(name); + + return getNextChar(true); + } else if (!keyword.equalsIgnoreCase("define")) { + // Basically ignore any commands we don't understand + // by simply queueing the text back to be read again + // but with the flag set to ignore this command (so + // we don't get into infinite recursion!) + ch = getNextChar(false); + queue(ret); + queue(keyword); + queue(ch); + + return getNextChar(false); + } + + // Define a macro + String name = getNextWord(); + StringBuilder buf = new StringBuilder(); + + // Skip whitespace after the macro name + do { + ch = getNextChar(true); + } while (ch != -1 && Character.isWhitespace(ch) && ch != '\\' && ch != '\n'); + + queue(ch); + + do { + while ((ch = getNextChar(true)) != -1 && ch != '\\' && ch != '\n') { + buf.append((char) ch); + } + + // Check for line continuation character + if (ch == '\\') { + int next = getNextChar(true); + if (next == '\n') { + buf.append((char) next); + } else { + buf.append((char) ch); + buf.append((char) next); + } + } + } while (ch != -1 && ch != '\n'); + + variableMap.put(name, buf.toString()); + + return getNextChar(true); + } else if (ret == '$') { + // Check for macro expansion + // Note: this allows for nested expansion + int next = getNextChar(true); + if (next == '{') { + StringBuilder buf = new StringBuilder(); + + while ((ch = getNextChar(true)) != -1 && ch != '}') { + buf.append((char) ch); + } + + String expansion = variableMap.get(buf.toString()); + if (expansion == null) { + // If the macro is undefined, then we put back the + // original text to be read again, but without expansion + queue(ret); + queue(next); + queue(buf); + queue(ch); + + ret = getNextChar(false); + } else { + queue(expansion); + + // By allowing expansion here, we get recursive expansion + ret = getNextChar(true); + } + } else { + queue(next); + } + } + } + + return (lastCh = ret); + } + + @Override + public int read(final char[] cbuf, final int off, final int len) throws IOException { + int read = -1; + for (int i = 0; i < len; i++) { + int ch = getNextChar(true); + if (ch == -1) { + break; + } + read = i; + cbuf[off + i] = (char) ch; + } + return (read == -1) ? read : read + 1; + } + +}
Propchange: pivot/trunk/core/src/org/apache/pivot/serialization/MacroReader.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: pivot/trunk/core/src/org/apache/pivot/text/AttributedStringCharacterIterator.java URL: http://svn.apache.org/viewvc/pivot/trunk/core/src/org/apache/pivot/text/AttributedStringCharacterIterator.java?rev=1913470&r1=1913469&r2=1913470&view=diff ============================================================================== --- pivot/trunk/core/src/org/apache/pivot/text/AttributedStringCharacterIterator.java (original) +++ pivot/trunk/core/src/org/apache/pivot/text/AttributedStringCharacterIterator.java Tue Oct 31 19:15:47 2023 @@ -1,243 +1,243 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.pivot.text; - -import java.awt.Font; -import java.awt.font.TextAttribute; -import java.text.AttributedCharacterIterator; -import java.text.AttributedString; -import java.util.Map; -import java.util.Set; -import org.apache.pivot.util.Utils; - - -/** - * A sequence of text along with associated attributes, backed by a - * {@link AttributedString}, which itself implements all of these - * methods. - */ -public class AttributedStringCharacterIterator implements AttributedCharacterIterator { - private AttributedString storage = null; - private int length = 0; - private AttributedCharacterIterator.Attribute[] attributes = null; - private AttributedCharacterIterator iterator = null; - - public AttributedStringCharacterIterator() { - this.storage = new AttributedString(""); - this.length = 0; - } - - public AttributedStringCharacterIterator(String text) { - Utils.checkNull(text, "text"); - this.storage = new AttributedString(text); - } - - private void addAttribute(AttributedCharacterIterator.Attribute attribute, Object value) { - if (this.length != 0) { - this.storage.addAttribute(attribute, value); - } - } - - public AttributedStringCharacterIterator(CharSequence charSequence, Font font) { - this.storage = new AttributedString(charSequence.toString()); - this.length = charSequence.length(); - addAttribute(TextAttribute.FONT, font); - } - - public AttributedStringCharacterIterator(CharSequence charSequence, int beginIndex, Font font) { - this.storage = new AttributedString(charSequence.subSequence(beginIndex, charSequence.length()).toString()); - this.length = charSequence.length() - beginIndex; - addAttribute(TextAttribute.FONT, font); - } - - public AttributedStringCharacterIterator(String text, int beginIndex, int endIndex) { - Utils.checkNull(text, "text"); - this.storage = new AttributedString(text.substring(beginIndex, endIndex)); - this.length = endIndex - beginIndex; - } - - public AttributedStringCharacterIterator(AttributedCharacterIterator iter, int beginIndex, int endIndex) { - this.storage = new AttributedString(iter, beginIndex, endIndex); - this.length = endIndex - beginIndex; - } - - public AttributedStringCharacterIterator(String text, AttributedCharacterIterator.Attribute[] attributes) { - Utils.checkNull(text, "text"); - this.storage = new AttributedString(text); - this.length = text.length(); - this.attributes = attributes; - } - - public AttributedStringCharacterIterator(String text, int beginIndex, int endIndex, - AttributedCharacterIterator.Attribute[] attributes) { - Utils.checkNull(text, "text"); - this.storage = new AttributedString(text.substring(beginIndex, endIndex)); - this.length = endIndex - beginIndex; - this.attributes = attributes; - } - - private AttributedStringCharacterIterator(AttributedString attributedString) { - // TODO: maybe we should make a copy? instead of adding the reference??? - this.storage = attributedString; - } - - // TODO: many more constructors needed, esp. those with attributes already in place - - public void addUnderlineAttribute(boolean underline) { - addAttribute(TextAttribute.UNDERLINE, - underline ? TextAttribute.UNDERLINE_ON : Integer.valueOf(-1)); - } - - public void addStrikethroughAttribute(boolean strikethrough) { - addAttribute(TextAttribute.STRIKETHROUGH, - strikethrough ? TextAttribute.STRIKETHROUGH_ON : Boolean.FALSE); - } - - // TODO: do we need more parameters here? for start position or anything else? - private AttributedCharacterIterator getIter() { - Utils.checkNull(this.storage, "source text"); - - if (this.iterator == null) { - if (this.attributes != null) { - this.iterator = storage.getIterator(attributes); - } else { - this.iterator = storage.getIterator(); - } - } - return this.iterator; - } - - /** - * Reset this iterator, meaning recreate the underlying iterator - * on the next call. - */ - public void reset() { - this.iterator = null; - } - - @Override - public Set<Attribute> getAllAttributeKeys() { - return getIter().getAllAttributeKeys(); - } - - @Override - public Object getAttribute(Attribute attribute) { - return getIter().getAttribute(attribute); - } - - @Override - public Map<Attribute, Object> getAttributes() { - return getIter().getAttributes(); - } - - @Override - public int getRunLimit() { - return getIter().getRunLimit(); - } - - @Override - public int getRunLimit(Attribute attribute) { - return getIter().getRunLimit(attribute); - } - - @Override - public int getRunLimit(Set<? extends Attribute> attributes) { - return getIter().getRunLimit(attributes); - } - - @Override - public int getRunStart() { - return getIter().getRunStart(); - } - - @Override - public int getRunStart(Attribute attribute) { - return getIter().getRunStart(attribute); - } - - @Override - public int getRunStart(Set<? extends Attribute> attributes) { - return getIter().getRunStart(attributes); - } - - @Override - public char first() { - return getIter().first(); - } - - @Override - public char last() { - return getIter().last(); - } - - @Override - public char current() { - return getIter().current(); - } - - @Override - public char next() { - return getIter().next(); - } - - @Override - public char previous() { - return getIter().previous(); - } - - @Override - public char setIndex(int position) { - return getIter().setIndex(position); - } - - @Override - public int getBeginIndex() { - return getIter().getBeginIndex(); - } - - @Override - public int getEndIndex() { - return getIter().getEndIndex(); - } - - @Override - public int getIndex() { - return getIter().getIndex(); - } - - @Override - public Object clone() { - AttributedStringCharacterIterator obj = new AttributedStringCharacterIterator(this.storage); - // Copy over the other fields - obj.length = this.length; - obj.attributes = this.attributes; - return obj; - } - - @Override - public String toString() { - AttributedCharacterIterator iter = storage.getIterator(); - StringBuilder buf = new StringBuilder(iter.getEndIndex()); - char ch = iter.first(); - while (ch != DONE) { - buf.append(ch); - ch = iter.next(); - } - return buf.toString(); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.pivot.text; + +import java.awt.Font; +import java.awt.font.TextAttribute; +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; +import java.util.Map; +import java.util.Set; +import org.apache.pivot.util.Utils; + + +/** + * A sequence of text along with associated attributes, backed by a + * {@link AttributedString}, which itself implements all of these + * methods. + */ +public class AttributedStringCharacterIterator implements AttributedCharacterIterator { + private AttributedString storage = null; + private int length = 0; + private AttributedCharacterIterator.Attribute[] attributes = null; + private AttributedCharacterIterator iterator = null; + + public AttributedStringCharacterIterator() { + this.storage = new AttributedString(""); + this.length = 0; + } + + public AttributedStringCharacterIterator(String text) { + Utils.checkNull(text, "text"); + this.storage = new AttributedString(text); + } + + private void addAttribute(AttributedCharacterIterator.Attribute attribute, Object value) { + if (this.length != 0) { + this.storage.addAttribute(attribute, value); + } + } + + public AttributedStringCharacterIterator(CharSequence charSequence, Font font) { + this.storage = new AttributedString(charSequence.toString()); + this.length = charSequence.length(); + addAttribute(TextAttribute.FONT, font); + } + + public AttributedStringCharacterIterator(CharSequence charSequence, int beginIndex, Font font) { + this.storage = new AttributedString(charSequence.subSequence(beginIndex, charSequence.length()).toString()); + this.length = charSequence.length() - beginIndex; + addAttribute(TextAttribute.FONT, font); + } + + public AttributedStringCharacterIterator(String text, int beginIndex, int endIndex) { + Utils.checkNull(text, "text"); + this.storage = new AttributedString(text.substring(beginIndex, endIndex)); + this.length = endIndex - beginIndex; + } + + public AttributedStringCharacterIterator(AttributedCharacterIterator iter, int beginIndex, int endIndex) { + this.storage = new AttributedString(iter, beginIndex, endIndex); + this.length = endIndex - beginIndex; + } + + public AttributedStringCharacterIterator(String text, AttributedCharacterIterator.Attribute[] attributes) { + Utils.checkNull(text, "text"); + this.storage = new AttributedString(text); + this.length = text.length(); + this.attributes = attributes; + } + + public AttributedStringCharacterIterator(String text, int beginIndex, int endIndex, + AttributedCharacterIterator.Attribute[] attributes) { + Utils.checkNull(text, "text"); + this.storage = new AttributedString(text.substring(beginIndex, endIndex)); + this.length = endIndex - beginIndex; + this.attributes = attributes; + } + + private AttributedStringCharacterIterator(AttributedString attributedString) { + // TODO: maybe we should make a copy? instead of adding the reference??? + this.storage = attributedString; + } + + // TODO: many more constructors needed, esp. those with attributes already in place + + public void addUnderlineAttribute(boolean underline) { + addAttribute(TextAttribute.UNDERLINE, + underline ? TextAttribute.UNDERLINE_ON : Integer.valueOf(-1)); + } + + public void addStrikethroughAttribute(boolean strikethrough) { + addAttribute(TextAttribute.STRIKETHROUGH, + strikethrough ? TextAttribute.STRIKETHROUGH_ON : Boolean.FALSE); + } + + // TODO: do we need more parameters here? for start position or anything else? + private AttributedCharacterIterator getIter() { + Utils.checkNull(this.storage, "source text"); + + if (this.iterator == null) { + if (this.attributes != null) { + this.iterator = storage.getIterator(attributes); + } else { + this.iterator = storage.getIterator(); + } + } + return this.iterator; + } + + /** + * Reset this iterator, meaning recreate the underlying iterator + * on the next call. + */ + public void reset() { + this.iterator = null; + } + + @Override + public Set<Attribute> getAllAttributeKeys() { + return getIter().getAllAttributeKeys(); + } + + @Override + public Object getAttribute(Attribute attribute) { + return getIter().getAttribute(attribute); + } + + @Override + public Map<Attribute, Object> getAttributes() { + return getIter().getAttributes(); + } + + @Override + public int getRunLimit() { + return getIter().getRunLimit(); + } + + @Override + public int getRunLimit(Attribute attribute) { + return getIter().getRunLimit(attribute); + } + + @Override + public int getRunLimit(Set<? extends Attribute> attributes) { + return getIter().getRunLimit(attributes); + } + + @Override + public int getRunStart() { + return getIter().getRunStart(); + } + + @Override + public int getRunStart(Attribute attribute) { + return getIter().getRunStart(attribute); + } + + @Override + public int getRunStart(Set<? extends Attribute> attributes) { + return getIter().getRunStart(attributes); + } + + @Override + public char first() { + return getIter().first(); + } + + @Override + public char last() { + return getIter().last(); + } + + @Override + public char current() { + return getIter().current(); + } + + @Override + public char next() { + return getIter().next(); + } + + @Override + public char previous() { + return getIter().previous(); + } + + @Override + public char setIndex(int position) { + return getIter().setIndex(position); + } + + @Override + public int getBeginIndex() { + return getIter().getBeginIndex(); + } + + @Override + public int getEndIndex() { + return getIter().getEndIndex(); + } + + @Override + public int getIndex() { + return getIter().getIndex(); + } + + @Override + public Object clone() { + AttributedStringCharacterIterator obj = new AttributedStringCharacterIterator(this.storage); + // Copy over the other fields + obj.length = this.length; + obj.attributes = this.attributes; + return obj; + } + + @Override + public String toString() { + AttributedCharacterIterator iter = storage.getIterator(); + StringBuilder buf = new StringBuilder(iter.getEndIndex()); + char ch = iter.first(); + while (ch != DONE) { + buf.append(ch); + ch = iter.next(); + } + return buf.toString(); + } + +} Propchange: pivot/trunk/core/src/org/apache/pivot/text/AttributedStringCharacterIterator.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java URL: http://svn.apache.org/viewvc/pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java?rev=1913470&r1=1913469&r2=1913470&view=diff ============================================================================== --- pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java (original) +++ pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java Tue Oct 31 19:15:47 2023 @@ -1,276 +1,276 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.pivot.text; - -import org.apache.pivot.collections.Dictionary; -import org.apache.pivot.collections.Sequence; -import org.apache.pivot.json.JSONSerializer; -import org.apache.pivot.serialization.SerializationException; -import org.apache.pivot.util.Utils; - -/** - * Immutable class representing a span of characters. The range includes all values - * in the interval <i><code>[start, start+length-1]</code></i> inclusive. This is - * the paradigm used in a lot of places (notably the text controls) to indicate a selection. - * <p> A zero-length span indicates a single caret position at the given start. - * <p> Negative lengths are not supported and will throw exceptions, as will - * negative start positions. - */ -public final class CharSpan { - /** The starting location of this span (zero-based). */ - public final int start; - /** The length of this span (non-negative). */ - public final int length; - - /** The dictionary key used to retrieve the start location. */ - public static final String START_KEY = "start"; - /** The dictionary key used to retrieve the length. */ - public static final String LENGTH_KEY = "length"; - - /** - * A span of length zero, starting at position zero. - */ - public static final CharSpan ZERO = new CharSpan(); - - - /** - * Construct a default span of length zero at location zero. - */ - public CharSpan() { - this(0); - } - - /** - * Construct a new char span of length zero at the given location. - * - * @param startValue The start of this char span. - * @throws IllegalArgumentException if the value is negative. - */ - public CharSpan(final int startValue) { - this(startValue, 0); - } - - /** - * Construct a new char span with the given values. - * - * @param startValue The start of this char span. - * @param lengthValue The length of this char span. - * @throws IllegalArgumentException if either value is negative. - */ - public CharSpan(final int startValue, final int lengthValue) { - Utils.checkNonNegative(startValue, "start"); - Utils.checkNonNegative(lengthValue, "length"); - - start = startValue; - length = lengthValue; - } - - /** - * Construct a new char span from another one (a "copy constructor"). - * - * @param existingCharSpan An existing char span (which must not be {@code null}). - * @throws IllegalArgumentException if the given char span is {@code null}. - */ - public CharSpan(final CharSpan existingCharSpan) { - Utils.checkNull(existingCharSpan, "existingCharSpan"); - - start = existingCharSpan.start; - length = existingCharSpan.length; - } - - /** - * Construct a new char span from the given dictionary which must contain - * the {@link #START_KEY} and can also contain the {@link #LENGTH_KEY} key. - * - * @param charSpanDictionary A dictionary containing start and length values. - * @throws IllegalArgumentException if the given char span is {@code null}, - * if the dictionary does not contain at least the start key, or if either of - * the dictionary values is negative. - */ - public CharSpan(final Dictionary<String, ?> charSpanDictionary) { - Utils.checkNull(charSpanDictionary, "charSpanDictionary"); - - int startValue; - int lengthValue = 0; - - if (charSpanDictionary.containsKey(START_KEY)) { - startValue = charSpanDictionary.getInt(START_KEY); - Utils.checkNonNegative(startValue, "start"); - } else { - throw new IllegalArgumentException(START_KEY + " is required."); - } - - if (charSpanDictionary.containsKey(LENGTH_KEY)) { - lengthValue = charSpanDictionary.getInt(LENGTH_KEY); - Utils.checkNonNegative(lengthValue, "length"); - } - - start = startValue; - length = lengthValue; - } - - /** - * Construct a new char span from the given sequence with two - * numeric values corresponding to the start and length values - * respectively, or one numeric value corresponding to the start - * value (length 0). - * - * @param charSpanSequence A sequence containing the start and length values. - * @throws IllegalArgumentException if the given char span is {@code null}, or - * zero length, or length is greater than two. - */ - public CharSpan(final Sequence<?> charSpanSequence) { - Utils.checkNull(charSpanSequence, "charSpanSequence"); - - int startValue; - int lengthValue = 0; - int seqLength = charSpanSequence.getLength(); - - if (seqLength < 1 || seqLength > 2) { - throw new IllegalArgumentException("CharSpan needs one or two values in the sequence to construct."); - } - - startValue = ((Number) charSpanSequence.get(0)).intValue(); - Utils.checkNonNegative(startValue, "start"); - - if (seqLength == 2) { - lengthValue = ((Number) charSpanSequence.get(1)).intValue(); - Utils.checkNonNegative(lengthValue, "length"); - } - - - start = startValue; - length = lengthValue; - } - - /** - * Returns the inclusive end value of this char span, which is the - * <code>start + length - 1</code>. So, if the length is zero, - * then the end will be less that the start. - * - * @return The computed inclusive end value of this char span. - */ - public int getEnd() { - return start + length - 1; - } - - /** - * Returns a new {@link CharSpan} with the start value offset by the given amount. - * - * @param offset The positive or negative amount by which to "move" this - * char span (the start value). - * @return A new {@link CharSpan} with the updated value. - * @throws IllegalArgumentException if the updated start value goes negative. - */ - public CharSpan offset(final int offset) { - return (offset == 0) ? this : new CharSpan(this.start + offset, this.length); - } - - /** - * Returns a new {@link CharSpan} with the length value offset by the given amount - * (either positive to lengthen the span or negative to shorten the span). - * - * @param offset The positive or negative amount by which to "lengthen" this - * char span (the length value). - * @return A new {@link CharSpan} with the updated value. - * @throws IllegalArgumentException if the updated length value goes negative. - */ - public CharSpan lengthen(final int offset) { - return (offset == 0) ? this : new CharSpan(this.start, this.length + offset); - } - - @Override - public boolean equals(final Object o) { - boolean equal = false; - - if (o == this) { - return true; - } - - if (o instanceof CharSpan) { - CharSpan span = (CharSpan) o; - equal = (start == span.start && length == span.length); - } - - return equal; - } - - @Override - public int hashCode() { - return 31 * start + length; - } - - @Override - public String toString() { - return getClass().getSimpleName() + " {start:" + start + ", length:" + length + "}"; - } - - /** - * Convert a string into a char span. - * <p> If the string value is a JSON map, then parse the map - * and construct using the {@link #CharSpan(Dictionary)} method. - * <p> If the string value is a JSON list, then parse the list - * and construct using the first two values as start and end - * respectively, using the {@link #CharSpan(int, int)} constructor. - * <p> Also accepted is a simple list of two integer values - * separated by comma or semicolon. - * <p> Otherwise the string should be a single integer value - * that will be used to construct the char span using the {@link #CharSpan(int)} - * constructor (just the start value, with a zero length). - * - * @param value The string value to decode into a new char span. - * @return The decoded char span. - * @throws IllegalArgumentException if the value is {@code null} or empty, - * if the string starts with <code>"{"</code> but it cannot be parsed as - * a JSON map, if it starts with <code>"["</code> but cannot be parsed - * as a JSON list, or cannot be recognized as a simple list of one or - * two integers. - */ - public static CharSpan decode(final String value) { - Utils.checkNullOrEmpty(value, "value"); - - CharSpan charSpan; - if (value.startsWith("{")) { - try { - charSpan = new CharSpan(JSONSerializer.parseMap(value)); - } catch (SerializationException exception) { - throw new IllegalArgumentException(exception); - } - } else if (value.startsWith("[")) { - try { - charSpan = new CharSpan(JSONSerializer.parseList(value)); - } catch (SerializationException exception) { - throw new IllegalArgumentException(exception); - } - } else { - String[] parts = value.split("\\s*[,;]\\s*"); - try { - if (parts.length == 2) { - charSpan = new CharSpan(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])); - } else if (parts.length == 1) { - charSpan = new CharSpan(Integer.parseInt(value)); - } else { - throw new IllegalArgumentException("Unknown format for CharSpan: " + value); - } - } catch (NumberFormatException ex) { - throw new IllegalArgumentException(ex); - } - } - - return charSpan; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.pivot.text; + +import org.apache.pivot.collections.Dictionary; +import org.apache.pivot.collections.Sequence; +import org.apache.pivot.json.JSONSerializer; +import org.apache.pivot.serialization.SerializationException; +import org.apache.pivot.util.Utils; + +/** + * Immutable class representing a span of characters. The range includes all values + * in the interval <i><code>[start, start+length-1]</code></i> inclusive. This is + * the paradigm used in a lot of places (notably the text controls) to indicate a selection. + * <p> A zero-length span indicates a single caret position at the given start. + * <p> Negative lengths are not supported and will throw exceptions, as will + * negative start positions. + */ +public final class CharSpan { + /** The starting location of this span (zero-based). */ + public final int start; + /** The length of this span (non-negative). */ + public final int length; + + /** The dictionary key used to retrieve the start location. */ + public static final String START_KEY = "start"; + /** The dictionary key used to retrieve the length. */ + public static final String LENGTH_KEY = "length"; + + /** + * A span of length zero, starting at position zero. + */ + public static final CharSpan ZERO = new CharSpan(); + + + /** + * Construct a default span of length zero at location zero. + */ + public CharSpan() { + this(0); + } + + /** + * Construct a new char span of length zero at the given location. + * + * @param startValue The start of this char span. + * @throws IllegalArgumentException if the value is negative. + */ + public CharSpan(final int startValue) { + this(startValue, 0); + } + + /** + * Construct a new char span with the given values. + * + * @param startValue The start of this char span. + * @param lengthValue The length of this char span. + * @throws IllegalArgumentException if either value is negative. + */ + public CharSpan(final int startValue, final int lengthValue) { + Utils.checkNonNegative(startValue, "start"); + Utils.checkNonNegative(lengthValue, "length"); + + start = startValue; + length = lengthValue; + } + + /** + * Construct a new char span from another one (a "copy constructor"). + * + * @param existingCharSpan An existing char span (which must not be {@code null}). + * @throws IllegalArgumentException if the given char span is {@code null}. + */ + public CharSpan(final CharSpan existingCharSpan) { + Utils.checkNull(existingCharSpan, "existingCharSpan"); + + start = existingCharSpan.start; + length = existingCharSpan.length; + } + + /** + * Construct a new char span from the given dictionary which must contain + * the {@link #START_KEY} and can also contain the {@link #LENGTH_KEY} key. + * + * @param charSpanDictionary A dictionary containing start and length values. + * @throws IllegalArgumentException if the given char span is {@code null}, + * if the dictionary does not contain at least the start key, or if either of + * the dictionary values is negative. + */ + public CharSpan(final Dictionary<String, ?> charSpanDictionary) { + Utils.checkNull(charSpanDictionary, "charSpanDictionary"); + + int startValue; + int lengthValue = 0; + + if (charSpanDictionary.containsKey(START_KEY)) { + startValue = charSpanDictionary.getInt(START_KEY); + Utils.checkNonNegative(startValue, "start"); + } else { + throw new IllegalArgumentException(START_KEY + " is required."); + } + + if (charSpanDictionary.containsKey(LENGTH_KEY)) { + lengthValue = charSpanDictionary.getInt(LENGTH_KEY); + Utils.checkNonNegative(lengthValue, "length"); + } + + start = startValue; + length = lengthValue; + } + + /** + * Construct a new char span from the given sequence with two + * numeric values corresponding to the start and length values + * respectively, or one numeric value corresponding to the start + * value (length 0). + * + * @param charSpanSequence A sequence containing the start and length values. + * @throws IllegalArgumentException if the given char span is {@code null}, or + * zero length, or length is greater than two. + */ + public CharSpan(final Sequence<?> charSpanSequence) { + Utils.checkNull(charSpanSequence, "charSpanSequence"); + + int startValue; + int lengthValue = 0; + int seqLength = charSpanSequence.getLength(); + + if (seqLength < 1 || seqLength > 2) { + throw new IllegalArgumentException("CharSpan needs one or two values in the sequence to construct."); + } + + startValue = ((Number) charSpanSequence.get(0)).intValue(); + Utils.checkNonNegative(startValue, "start"); + + if (seqLength == 2) { + lengthValue = ((Number) charSpanSequence.get(1)).intValue(); + Utils.checkNonNegative(lengthValue, "length"); + } + + + start = startValue; + length = lengthValue; + } + + /** + * Returns the inclusive end value of this char span, which is the + * <code>start + length - 1</code>. So, if the length is zero, + * then the end will be less that the start. + * + * @return The computed inclusive end value of this char span. + */ + public int getEnd() { + return start + length - 1; + } + + /** + * Returns a new {@link CharSpan} with the start value offset by the given amount. + * + * @param offset The positive or negative amount by which to "move" this + * char span (the start value). + * @return A new {@link CharSpan} with the updated value. + * @throws IllegalArgumentException if the updated start value goes negative. + */ + public CharSpan offset(final int offset) { + return (offset == 0) ? this : new CharSpan(this.start + offset, this.length); + } + + /** + * Returns a new {@link CharSpan} with the length value offset by the given amount + * (either positive to lengthen the span or negative to shorten the span). + * + * @param offset The positive or negative amount by which to "lengthen" this + * char span (the length value). + * @return A new {@link CharSpan} with the updated value. + * @throws IllegalArgumentException if the updated length value goes negative. + */ + public CharSpan lengthen(final int offset) { + return (offset == 0) ? this : new CharSpan(this.start, this.length + offset); + } + + @Override + public boolean equals(final Object o) { + boolean equal = false; + + if (o == this) { + return true; + } + + if (o instanceof CharSpan) { + CharSpan span = (CharSpan) o; + equal = (start == span.start && length == span.length); + } + + return equal; + } + + @Override + public int hashCode() { + return 31 * start + length; + } + + @Override + public String toString() { + return getClass().getSimpleName() + " {start:" + start + ", length:" + length + "}"; + } + + /** + * Convert a string into a char span. + * <p> If the string value is a JSON map, then parse the map + * and construct using the {@link #CharSpan(Dictionary)} method. + * <p> If the string value is a JSON list, then parse the list + * and construct using the first two values as start and end + * respectively, using the {@link #CharSpan(int, int)} constructor. + * <p> Also accepted is a simple list of two integer values + * separated by comma or semicolon. + * <p> Otherwise the string should be a single integer value + * that will be used to construct the char span using the {@link #CharSpan(int)} + * constructor (just the start value, with a zero length). + * + * @param value The string value to decode into a new char span. + * @return The decoded char span. + * @throws IllegalArgumentException if the value is {@code null} or empty, + * if the string starts with <code>"{"</code> but it cannot be parsed as + * a JSON map, if it starts with <code>"["</code> but cannot be parsed + * as a JSON list, or cannot be recognized as a simple list of one or + * two integers. + */ + public static CharSpan decode(final String value) { + Utils.checkNullOrEmpty(value, "value"); + + CharSpan charSpan; + if (value.startsWith("{")) { + try { + charSpan = new CharSpan(JSONSerializer.parseMap(value)); + } catch (SerializationException exception) { + throw new IllegalArgumentException(exception); + } + } else if (value.startsWith("[")) { + try { + charSpan = new CharSpan(JSONSerializer.parseList(value)); + } catch (SerializationException exception) { + throw new IllegalArgumentException(exception); + } + } else { + String[] parts = value.split("\\s*[,;]\\s*"); + try { + if (parts.length == 2) { + charSpan = new CharSpan(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])); + } else if (parts.length == 1) { + charSpan = new CharSpan(Integer.parseInt(value)); + } else { + throw new IllegalArgumentException("Unknown format for CharSpan: " + value); + } + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(ex); + } + } + + return charSpan; + } +} Propchange: pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: pivot/trunk/core/src/org/apache/pivot/text/CompositeIterator.java URL: http://svn.apache.org/viewvc/pivot/trunk/core/src/org/apache/pivot/text/CompositeIterator.java?rev=1913470&r1=1913469&r2=1913470&view=diff ============================================================================== --- pivot/trunk/core/src/org/apache/pivot/text/CompositeIterator.java (original) +++ pivot/trunk/core/src/org/apache/pivot/text/CompositeIterator.java Tue Oct 31 19:15:47 2023 @@ -1,185 +1,185 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.pivot.text; - -import java.text.AttributedCharacterIterator; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import org.apache.pivot.collections.ArrayList; -import org.apache.pivot.collections.List; -import org.apache.pivot.util.Utils; - - -/** - * An {@link AttributedCharacterIterator} that implements iterating over - * several (typically two or three) underlying iterators. The assumption - * is that no attributes will ever cross the underlying iterator boundaries - * (which is always the case for insertion of composed but uncommitted text - * in the middle of two pieces of committed text (for instance). - */ -public class CompositeIterator implements AttributedCharacterIterator { - - private List<AttributedCharacterIterator> iterators = new ArrayList<>(); - private int endIndex; - private int currentIndex; - private AttributedCharacterIterator currentIterator; - private int currentIteratorDelta; - - /** - * Constructs a CompositeIterator that iterates over the concatenation - * of one or more iterators. - * @param iterators The base iterators that this composite iterator concatenates. - */ - public CompositeIterator(AttributedCharacterIterator... iterators) { - int fullLength = 0; - for (AttributedCharacterIterator iter : iterators) { - int beginIndex = iter.getBeginIndex(); // inclusive - int endIndex = iter.getEndIndex(); // exclusive - int range = (endIndex - beginIndex); - this.iterators.add(iter); - fullLength += range; - } - this.endIndex = fullLength; - setIndex0(0); - } - - // CharacterIterator implementations - - public char first() { - return setIndex0(0); - } - - public char last() { - if (endIndex == 0) { - return setIndex0(endIndex); - } else { - return setIndex0(endIndex - 1); - } - } - - public char next() { - if (currentIndex < endIndex) { - return setIndex0(currentIndex + 1); - } else { - return DONE; - } - } - - public char previous() { - if (currentIndex > 0) { - return setIndex0(currentIndex - 1); - } else { - return DONE; - } - } - - public char current() { - return currentIterator.setIndex(currentIndex - currentIteratorDelta); - } - - public char setIndex(int position) { - // Note: this is a (0 < position <= endIndex) check, since "endIndex" is a valid value here - Utils.checkIndexBounds(position, 0, endIndex); - - return setIndex0(position); - } - - private char setIndex0(int position) { - currentIndex = position; - int cumLength = 0; - for (AttributedCharacterIterator iter : iterators) { - int beginIndex = iter.getBeginIndex(); - int endIndex = iter.getEndIndex(); - int range = endIndex - beginIndex; - if (currentIndex < endIndex + cumLength) { - currentIterator = iter; - currentIteratorDelta = beginIndex + cumLength; - // TODO: not sure this is going to be correct for > 2 iterators - return currentIterator.setIndex(currentIndex - currentIteratorDelta); - } - cumLength += range; - } - return DONE; - } - - public int getBeginIndex() { - return 0; - } - - public int getEndIndex() { - return endIndex; - } - - public int getIndex() { - return currentIndex; - } - - // AttributedCharacterIterator implementations - - public int getRunStart() { - return currentIterator.getRunStart() + currentIteratorDelta; - } - - public int getRunLimit() { - return currentIterator.getRunLimit() + currentIteratorDelta; - } - - public int getRunStart(Attribute attribute) { - return currentIterator.getRunStart(attribute) + currentIteratorDelta; - } - - public int getRunLimit(Attribute attribute) { - return currentIterator.getRunLimit(attribute) + currentIteratorDelta; - } - - public int getRunStart(Set<? extends Attribute> attributes) { - return currentIterator.getRunStart(attributes) + currentIteratorDelta; - } - - public int getRunLimit(Set<? extends Attribute> attributes) { - return currentIterator.getRunLimit(attributes) + currentIteratorDelta; - } - - public Map<Attribute, Object> getAttributes() { - return currentIterator.getAttributes(); - } - - public Set<Attribute> getAllAttributeKeys() { - Set<Attribute> keys = new HashSet<>(); - for (AttributedCharacterIterator iter : iterators) { - keys.addAll(iter.getAllAttributeKeys()); - } - return keys; - } - - public Object getAttribute(Attribute attribute) { - return currentIterator.getAttribute(attribute); - } - - // Object implementations - - public Object clone() { - try { - CompositeIterator other = (CompositeIterator) super.clone(); - return other; - } catch (CloneNotSupportedException e) { - throw new InternalError(); - } - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.pivot.text; + +import java.text.AttributedCharacterIterator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.apache.pivot.collections.ArrayList; +import org.apache.pivot.collections.List; +import org.apache.pivot.util.Utils; + + +/** + * An {@link AttributedCharacterIterator} that implements iterating over + * several (typically two or three) underlying iterators. The assumption + * is that no attributes will ever cross the underlying iterator boundaries + * (which is always the case for insertion of composed but uncommitted text + * in the middle of two pieces of committed text (for instance). + */ +public class CompositeIterator implements AttributedCharacterIterator { + + private List<AttributedCharacterIterator> iterators = new ArrayList<>(); + private int endIndex; + private int currentIndex; + private AttributedCharacterIterator currentIterator; + private int currentIteratorDelta; + + /** + * Constructs a CompositeIterator that iterates over the concatenation + * of one or more iterators. + * @param iterators The base iterators that this composite iterator concatenates. + */ + public CompositeIterator(AttributedCharacterIterator... iterators) { + int fullLength = 0; + for (AttributedCharacterIterator iter : iterators) { + int beginIndex = iter.getBeginIndex(); // inclusive + int endIndex = iter.getEndIndex(); // exclusive + int range = (endIndex - beginIndex); + this.iterators.add(iter); + fullLength += range; + } + this.endIndex = fullLength; + setIndex0(0); + } + + // CharacterIterator implementations + + public char first() { + return setIndex0(0); + } + + public char last() { + if (endIndex == 0) { + return setIndex0(endIndex); + } else { + return setIndex0(endIndex - 1); + } + } + + public char next() { + if (currentIndex < endIndex) { + return setIndex0(currentIndex + 1); + } else { + return DONE; + } + } + + public char previous() { + if (currentIndex > 0) { + return setIndex0(currentIndex - 1); + } else { + return DONE; + } + } + + public char current() { + return currentIterator.setIndex(currentIndex - currentIteratorDelta); + } + + public char setIndex(int position) { + // Note: this is a (0 < position <= endIndex) check, since "endIndex" is a valid value here + Utils.checkIndexBounds(position, 0, endIndex); + + return setIndex0(position); + } + + private char setIndex0(int position) { + currentIndex = position; + int cumLength = 0; + for (AttributedCharacterIterator iter : iterators) { + int beginIndex = iter.getBeginIndex(); + int endIndex = iter.getEndIndex(); + int range = endIndex - beginIndex; + if (currentIndex < endIndex + cumLength) { + currentIterator = iter; + currentIteratorDelta = beginIndex + cumLength; + // TODO: not sure this is going to be correct for > 2 iterators + return currentIterator.setIndex(currentIndex - currentIteratorDelta); + } + cumLength += range; + } + return DONE; + } + + public int getBeginIndex() { + return 0; + } + + public int getEndIndex() { + return endIndex; + } + + public int getIndex() { + return currentIndex; + } + + // AttributedCharacterIterator implementations + + public int getRunStart() { + return currentIterator.getRunStart() + currentIteratorDelta; + } + + public int getRunLimit() { + return currentIterator.getRunLimit() + currentIteratorDelta; + } + + public int getRunStart(Attribute attribute) { + return currentIterator.getRunStart(attribute) + currentIteratorDelta; + } + + public int getRunLimit(Attribute attribute) { + return currentIterator.getRunLimit(attribute) + currentIteratorDelta; + } + + public int getRunStart(Set<? extends Attribute> attributes) { + return currentIterator.getRunStart(attributes) + currentIteratorDelta; + } + + public int getRunLimit(Set<? extends Attribute> attributes) { + return currentIterator.getRunLimit(attributes) + currentIteratorDelta; + } + + public Map<Attribute, Object> getAttributes() { + return currentIterator.getAttributes(); + } + + public Set<Attribute> getAllAttributeKeys() { + Set<Attribute> keys = new HashSet<>(); + for (AttributedCharacterIterator iter : iterators) { + keys.addAll(iter.getAllAttributeKeys()); + } + return keys; + } + + public Object getAttribute(Attribute attribute) { + return currentIterator.getAttribute(attribute); + } + + // Object implementations + + public Object clone() { + try { + CompositeIterator other = (CompositeIterator) super.clone(); + return other; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + +} Propchange: pivot/trunk/core/src/org/apache/pivot/text/CompositeIterator.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: pivot/trunk/core/src/org/apache/pivot/util/BooleanResult.java URL: http://svn.apache.org/viewvc/pivot/trunk/core/src/org/apache/pivot/util/BooleanResult.java?rev=1913470&r1=1913469&r2=1913470&view=diff ============================================================================== --- pivot/trunk/core/src/org/apache/pivot/util/BooleanResult.java (original) +++ pivot/trunk/core/src/org/apache/pivot/util/BooleanResult.java Tue Oct 31 19:15:47 2023 @@ -1,108 +1,108 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.pivot.util; - -/** - * An object holding a boolean value that can be used with a {@code forEach} - * or lambda expressionm, where the value used during the iteration must be - * final or effectively final. - */ -public class BooleanResult { - /** - * The current boolean value. - */ - private boolean result; - - /** - * Construct one of these and set the initial value to the given value. - * - * @param initialValue The initial boolean value. - */ - public BooleanResult(final boolean initialValue) { - result = initialValue; - } - - /** - * Construct one of these and set the initial boolean value to - * {@code false}. - */ - public BooleanResult() { - this(false); - } - - /** - * Update the saved value by <code>OR</code>ing this new value - * with the saved value. - * - * @param value The new value to OR into the saved one. - */ - public void or(final boolean value) { - result |= value; - } - - /** - * Update the saved value by <code>AND</code>ing this new value - * with the saved value. - * - * @param value The new value to AND into the saved one. - */ - public void and(final boolean value) { - result &= value; - } - - /** - * Update the saved value by <code>XOR</code>ing this new value - * with the saved value. - * - * @param value The new value to XOR into the saved one. - */ - public void xor(final boolean value) { - result ^= value; - } - - /** - * Negate the saved value. - */ - public void not() { - result = !result; - } - - /** - * Clear the boolean result to the default value - * of {@code false}. - */ - public void clear() { - result = false; - } - - /** - * Set the result value to the given value. - * - * @param value The new value to set. - */ - public void set(final boolean value) { - result = value; - } - - /** - * @return The final boolean value. - */ - public boolean get() { - return result; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.pivot.util; + +/** + * An object holding a boolean value that can be used with a {@code forEach} + * or lambda expressionm, where the value used during the iteration must be + * final or effectively final. + */ +public class BooleanResult { + /** + * The current boolean value. + */ + private boolean result; + + /** + * Construct one of these and set the initial value to the given value. + * + * @param initialValue The initial boolean value. + */ + public BooleanResult(final boolean initialValue) { + result = initialValue; + } + + /** + * Construct one of these and set the initial boolean value to + * {@code false}. + */ + public BooleanResult() { + this(false); + } + + /** + * Update the saved value by <code>OR</code>ing this new value + * with the saved value. + * + * @param value The new value to OR into the saved one. + */ + public void or(final boolean value) { + result |= value; + } + + /** + * Update the saved value by <code>AND</code>ing this new value + * with the saved value. + * + * @param value The new value to AND into the saved one. + */ + public void and(final boolean value) { + result &= value; + } + + /** + * Update the saved value by <code>XOR</code>ing this new value + * with the saved value. + * + * @param value The new value to XOR into the saved one. + */ + public void xor(final boolean value) { + result ^= value; + } + + /** + * Negate the saved value. + */ + public void not() { + result = !result; + } + + /** + * Clear the boolean result to the default value + * of {@code false}. + */ + public void clear() { + result = false; + } + + /** + * Set the result value to the given value. + * + * @param value The new value to set. + */ + public void set(final boolean value) { + result = value; + } + + /** + * @return The final boolean value. + */ + public boolean get() { + return result; + } + +} Propchange: pivot/trunk/core/src/org/apache/pivot/util/BooleanResult.java ------------------------------------------------------------------------------ svn:eol-style = native