1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075 |
- /*
- * 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.
- */
-
- /* $Id$ */
-
- package org.apache.fop.complexscripts.util;
-
- import java.nio.IntBuffer;
-
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- // CSOFF: LineLengthCheck
-
- /**
- * <p>A GlyphSequence encapsulates a sequence of character codes, a sequence of glyph codes,
- * and a sequence of character associations, where, for each glyph in the sequence of glyph
- * codes, there is a corresponding character association. Character associations server to
- * relate the glyph codes in a glyph sequence to the specific characters in an original
- * character code sequence with which the glyph codes are associated.</p>
- *
- * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
- */
- public class GlyphSequence implements Cloneable {
-
- /** default character buffer capacity in case new character buffer is created */
- private static final int DEFAULT_CHARS_CAPACITY = 8;
-
- /** character buffer */
- private IntBuffer characters;
- /** glyph buffer */
- private IntBuffer glyphs;
- /** association list */
- private List associations;
- /** predications flag */
- private boolean predications;
-
- /**
- * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced
- * character and glyph buffers and associations. If characters is null, then
- * an empty character buffer is created. If glyphs is null, then a glyph buffer
- * is created whose capacity is that of the character buffer. If associations is
- * null, then identity associations are created.
- * @param characters a (possibly null) buffer of associated (originating) characters
- * @param glyphs a (possibly null) buffer of glyphs
- * @param associations a (possibly null) array of glyph to character associations
- * @param predications true if predications are enabled
- */
- public GlyphSequence(IntBuffer characters, IntBuffer glyphs, List associations, boolean predications) {
- if (characters == null) {
- characters = IntBuffer.allocate(DEFAULT_CHARS_CAPACITY);
- }
- if (glyphs == null) {
- glyphs = IntBuffer.allocate(characters.capacity());
- }
- if (associations == null) {
- associations = makeIdentityAssociations(characters.limit(), glyphs.limit());
- }
- this.characters = characters;
- this.glyphs = glyphs;
- this.associations = associations;
- this.predications = predications;
- }
-
- /**
- * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced
- * character and glyph buffers and associations. If characters is null, then
- * an empty character buffer is created. If glyphs is null, then a glyph buffer
- * is created whose capacity is that of the character buffer. If associations is
- * null, then identity associations are created.
- * @param characters a (possibly null) buffer of associated (originating) characters
- * @param glyphs a (possibly null) buffer of glyphs
- * @param associations a (possibly null) array of glyph to character associations
- */
- public GlyphSequence(IntBuffer characters, IntBuffer glyphs, List associations) {
- this (characters, glyphs, associations, false);
- }
-
- /**
- * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
- * the character array of the existing sequence (but not the buffer object), and creates new copies
- * of glyphs buffer and association list.
- * @param gs an existing glyph sequence
- */
- public GlyphSequence(GlyphSequence gs) {
- this (gs.characters.duplicate(), copyBuffer(gs.glyphs), copyAssociations(gs.associations), gs.predications);
- }
-
- /**
- * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
- * the character array of the existing sequence (but not the buffer object), but uses the specified
- * backtrack, input, and lookahead glyph arrays to populate the glyphs, and uses the specified
- * of glyphs buffer and association list.
- * backtrack, input, and lookahead association arrays to populate the associations.
- * @param gs an existing glyph sequence
- * @param bga backtrack glyph array
- * @param iga input glyph array
- * @param lga lookahead glyph array
- * @param bal backtrack association list
- * @param ial input association list
- * @param lal lookahead association list
- */
- public GlyphSequence(GlyphSequence gs, int[] bga, int[] iga, int[] lga, CharAssociation[] bal, CharAssociation[] ial, CharAssociation[] lal) {
- this (gs.characters.duplicate(), concatGlyphs(bga, iga, lga), concatAssociations(bal, ial, lal), gs.predications);
- }
-
- /**
- * Obtain reference to underlying character buffer.
- * @return character buffer reference
- */
- public IntBuffer getCharacters() {
- return characters;
- }
-
- /**
- * Obtain array of characters. If <code>copy</code> is true, then
- * a newly instantiated array is returned, otherwise a reference to
- * the underlying buffer's array is returned. N.B. in case a reference
- * to the undelying buffer's array is returned, the length
- * of the array is not necessarily the number of characters in array.
- * To determine the number of characters, use {@link #getCharacterCount}.
- * @param copy true if to return a newly instantiated array of characters
- * @return array of characters
- */
- public int[] getCharacterArray(boolean copy) {
- if (copy) {
- return toArray(characters);
- } else {
- return characters.array();
- }
- }
-
- /**
- * Obtain the number of characters in character array, where
- * each character constitutes a unicode scalar value.
- * @return number of characters available in character array
- */
- public int getCharacterCount() {
- return characters.limit();
- }
-
- /**
- * Obtain glyph id at specified index.
- * @param index to obtain glyph
- * @return the glyph identifier of glyph at specified index
- * @throws IndexOutOfBoundsException if index is less than zero
- * or exceeds last valid position
- */
- public int getGlyph(int index) throws IndexOutOfBoundsException {
- return glyphs.get(index);
- }
-
- /**
- * Set glyph id at specified index.
- * @param index to set glyph
- * @param gi glyph index
- * @throws IndexOutOfBoundsException if index is greater or equal to
- * the limit of the underlying glyph buffer
- */
- public void setGlyph(int index, int gi) throws IndexOutOfBoundsException {
- if (gi > 65535) {
- gi = 65535;
- }
- glyphs.put(index, gi);
- }
-
- /**
- * Obtain reference to underlying glyph buffer.
- * @return glyph buffer reference
- */
- public IntBuffer getGlyphs() {
- return glyphs;
- }
-
- /**
- * Obtain count glyphs starting at offset. If <code>count</code> is
- * negative, then it is treated as if the number of available glyphs
- * were specified.
- * @param offset into glyph sequence
- * @param count of glyphs to obtain starting at offset, or negative,
- * indicating all avaialble glyphs starting at offset
- * @return glyph array
- */
- public int[] getGlyphs(int offset, int count) {
- int ng = getGlyphCount();
- if (offset < 0) {
- offset = 0;
- } else if (offset > ng) {
- offset = ng;
- }
- if (count < 0) {
- count = ng - offset;
- }
- int[] ga = new int [ count ];
- for (int i = offset, n = offset + count, k = 0; i < n; i++) {
- if (k < ga.length) {
- ga [ k++ ] = glyphs.get(i);
- }
- }
- return ga;
- }
-
- /**
- * Obtain array of glyphs. If <code>copy</code> is true, then
- * a newly instantiated array is returned, otherwise a reference to
- * the underlying buffer's array is returned. N.B. in case a reference
- * to the undelying buffer's array is returned, the length
- * of the array is not necessarily the number of glyphs in array.
- * To determine the number of glyphs, use {@link #getGlyphCount}.
- * @param copy true if to return a newly instantiated array of glyphs
- * @return array of glyphs
- */
- public int[] getGlyphArray(boolean copy) {
- if (copy) {
- return toArray(glyphs);
- } else {
- return glyphs.array();
- }
- }
-
- /**
- * Obtain the number of glyphs in glyphs array, where
- * each glyph constitutes a font specific glyph index.
- * @return number of glyphs available in character array
- */
- public int getGlyphCount() {
- return glyphs.limit();
- }
-
- /**
- * Obtain association at specified index.
- * @param index into associations array
- * @return glyph to character associations at specified index
- * @throws IndexOutOfBoundsException if index is less than zero
- * or exceeds last valid position
- */
- public CharAssociation getAssociation(int index) throws IndexOutOfBoundsException {
- return (CharAssociation) associations.get(index);
- }
-
- /**
- * Obtain reference to underlying associations list.
- * @return associations list
- */
- public List getAssociations() {
- return associations;
- }
-
- /**
- * Obtain count associations starting at offset.
- * @param offset into glyph sequence
- * @param count of associations to obtain starting at offset, or negative,
- * indicating all avaialble associations starting at offset
- * @return associations
- */
- public CharAssociation[] getAssociations(int offset, int count) {
- int ng = getGlyphCount();
- if (offset < 0) {
- offset = 0;
- } else if (offset > ng) {
- offset = ng;
- }
- if (count < 0) {
- count = ng - offset;
- }
- CharAssociation[] aa = new CharAssociation [ count ];
- for (int i = offset, n = offset + count, k = 0; i < n; i++) {
- if (k < aa.length) {
- aa [ k++ ] = (CharAssociation) associations.get(i);
- }
- }
- return aa;
- }
-
- /**
- * Enable or disable predications.
- * @param enable true if predications are to be enabled; otherwise false to disable
- */
- public void setPredications(boolean enable) {
- this.predications = enable;
- }
-
- /**
- * Obtain predications state.
- * @return true if predications are enabled
- */
- public boolean getPredications() {
- return this.predications;
- }
-
- /**
- * Set predication <KEY,VALUE> at glyph sequence OFFSET.
- * @param offset offset (index) into glyph sequence
- * @param key predication key
- * @param value predication value
- */
- public void setPredication(int offset, String key, Object value) {
- if (predications) {
- CharAssociation[] aa = getAssociations(offset, 1);
- CharAssociation ca = aa[0];
- ca.setPredication(key, value);
- }
- }
-
- /**
- * Get predication KEY at glyph sequence OFFSET.
- * @param offset offset (index) into glyph sequence
- * @param key predication key
- * @return predication KEY at OFFSET or null if none exists
- */
- public Object getPredication(int offset, String key) {
- if (predications) {
- CharAssociation[] aa = getAssociations(offset, 1);
- CharAssociation ca = aa[0];
- return ca.getPredication(key);
- } else {
- return null;
- }
- }
-
- /**
- * Compare glyphs.
- * @param gb buffer containing glyph indices with which this glyph sequence's glyphs are to be compared
- * @return zero if glyphs are the same, otherwise returns 1 or -1 according to whether this glyph sequence's
- * glyphs are lexicographically greater or lesser than the glyphs in the specified string buffer
- */
- public int compareGlyphs(IntBuffer gb) {
- int ng = getGlyphCount();
- for (int i = 0, n = gb.limit(); i < n; i++) {
- if (i < ng) {
- int g1 = glyphs.get(i);
- int g2 = gb.get(i);
- if (g1 > g2) {
- return 1;
- } else if (g1 < g2) {
- return -1;
- }
- } else {
- return -1; // this gb is a proper prefix of specified gb
- }
- }
- return 0; // same lengths with no difference
- }
-
- /** {@inheritDoc} */
- public Object clone() {
- try {
- GlyphSequence gs = (GlyphSequence) super.clone();
- gs.characters = copyBuffer(characters);
- gs.glyphs = copyBuffer(glyphs);
- gs.associations = copyAssociations(associations);
- return gs;
- } catch (CloneNotSupportedException e) {
- return null;
- }
- }
-
- /** {@inheritDoc} */
- public String toString() {
- StringBuffer sb = new StringBuffer();
- sb.append('{');
- sb.append("chars = [");
- sb.append(characters);
- sb.append("], glyphs = [");
- sb.append(glyphs);
- sb.append("], associations = [");
- sb.append(associations);
- sb.append("]");
- sb.append('}');
- return sb.toString();
- }
-
- /**
- * Determine if two arrays of glyphs are identical.
- * @param ga1 first glyph array
- * @param ga2 second glyph array
- * @return true if arrays are botth null or both non-null and have identical elements
- */
- public static boolean sameGlyphs(int[] ga1, int[] ga2) {
- if (ga1 == ga2) {
- return true;
- } else if ((ga1 == null) || (ga2 == null)) {
- return false;
- } else if (ga1.length != ga2.length) {
- return false;
- } else {
- for (int i = 0, n = ga1.length; i < n; i++) {
- if (ga1[i] != ga2[i]) {
- return false;
- }
- }
- return true;
- }
- }
-
- /**
- * Concatenante glyph arrays.
- * @param bga backtrack glyph array
- * @param iga input glyph array
- * @param lga lookahead glyph array
- * @return new integer buffer containing concatenated glyphs
- */
- public static IntBuffer concatGlyphs(int[] bga, int[] iga, int[] lga) {
- int ng = 0;
- if (bga != null) {
- ng += bga.length;
- }
- if (iga != null) {
- ng += iga.length;
- }
- if (lga != null) {
- ng += lga.length;
- }
- IntBuffer gb = IntBuffer.allocate(ng);
- if (bga != null) {
- gb.put(bga);
- }
- if (iga != null) {
- gb.put(iga);
- }
- if (lga != null) {
- gb.put(lga);
- }
- gb.flip();
- return gb;
- }
-
- /**
- * Concatenante association arrays.
- * @param baa backtrack association array
- * @param iaa input association array
- * @param laa lookahead association array
- * @return new list containing concatenated associations
- */
- public static List concatAssociations(CharAssociation[] baa, CharAssociation[] iaa, CharAssociation[] laa) {
- int na = 0;
- if (baa != null) {
- na += baa.length;
- }
- if (iaa != null) {
- na += iaa.length;
- }
- if (laa != null) {
- na += laa.length;
- }
- if (na > 0) {
- List gl = new ArrayList(na);
- if (baa != null) {
- for (int i = 0; i < baa.length; i++) {
- gl.add(baa[i]);
- }
- }
- if (iaa != null) {
- for (int i = 0; i < iaa.length; i++) {
- gl.add(iaa[i]);
- }
- }
- if (laa != null) {
- for (int i = 0; i < laa.length; i++) {
- gl.add(laa[i]);
- }
- }
- return gl;
- } else {
- return null;
- }
- }
-
- /**
- * Join (concatenate) glyph sequences.
- * @param gs original glyph sequence from which to reuse character array reference
- * @param sa array of glyph sequences, whose glyph arrays and association lists are to be concatenated
- * @return new glyph sequence referring to character array of GS and concatenated glyphs and associations of SA
- */
- public static GlyphSequence join(GlyphSequence gs, GlyphSequence[] sa) {
- assert sa != null;
- int tg = 0;
- int ta = 0;
- for (int i = 0, n = sa.length; i < n; i++) {
- GlyphSequence s = sa [ i ];
- IntBuffer ga = s.getGlyphs();
- assert ga != null;
- int ng = ga.limit();
- List al = s.getAssociations();
- assert al != null;
- int na = al.size();
- assert na == ng;
- tg += ng;
- ta += na;
- }
- IntBuffer uga = IntBuffer.allocate(tg);
- ArrayList ual = new ArrayList(ta);
- for (int i = 0, n = sa.length; i < n; i++) {
- GlyphSequence s = sa [ i ];
- uga.put(s.getGlyphs());
- ual.addAll(s.getAssociations());
- }
- return new GlyphSequence(gs.getCharacters(), uga, ual, gs.getPredications());
- }
-
- /**
- * Reorder sequence such that [SOURCE,SOURCE+COUNT) is moved just prior to TARGET.
- * @param gs input sequence
- * @param source index of sub-sequence to reorder
- * @param count length of sub-sequence to reorder
- * @param target index to which source sub-sequence is to be moved
- * @return reordered sequence (or original if no reordering performed)
- */
- public static GlyphSequence reorder(GlyphSequence gs, int source, int count, int target) {
- if (source != target) {
- int ng = gs.getGlyphCount();
- int[] ga = gs.getGlyphArray(false);
- int[] nga = new int [ ng ];
- GlyphSequence.CharAssociation[] aa = gs.getAssociations(0, ng);
- GlyphSequence.CharAssociation[] naa = new GlyphSequence.CharAssociation [ ng ];
- if (source < target) {
- int t = 0;
- for (int s = 0, e = source; s < e; s++, t++) {
- nga[t] = ga[s];
- naa[t] = aa[s];
- }
- for (int s = source + count, e = target; s < e; s++, t++) {
- nga[t] = ga[s];
- naa[t] = aa[s];
- }
- for (int s = source, e = source + count; s < e; s++, t++) {
- nga[t] = ga[s];
- naa[t] = aa[s];
- }
- for (int s = target, e = ng; s < e; s++, t++) {
- nga[t] = ga[s];
- naa[t] = aa[s];
- }
- } else {
- int t = 0;
- for (int s = 0, e = target; s < e; s++, t++) {
- nga[t] = ga[s];
- naa[t] = aa[s];
- }
- for (int s = source, e = source + count; s < e; s++, t++) {
- nga[t] = ga[s];
- naa[t] = aa[s];
- }
- for (int s = target, e = source; s < e; s++, t++) {
- nga[t] = ga[s];
- naa[t] = aa[s];
- }
- for (int s = source + count, e = ng; s < e; s++, t++) {
- nga[t] = ga[s];
- naa[t] = aa[s];
- }
- }
- return new GlyphSequence(gs, null, nga, null, null, naa, null);
- } else {
- return gs;
- }
- }
-
- private static int[] toArray(IntBuffer ib) {
- if (ib != null) {
- int n = ib.limit();
- int[] ia = new int[n];
- ib.get(ia, 0, n);
- return ia;
- } else {
- return new int[0];
- }
- }
-
- private static List makeIdentityAssociations(int numChars, int numGlyphs) {
- int nc = numChars;
- int ng = numGlyphs;
- List av = new ArrayList(ng);
- for (int i = 0, n = ng; i < n; i++) {
- int k = (i > nc) ? nc : i;
- av.add(new CharAssociation(i, (k == nc) ? 0 : 1));
- }
- return av;
- }
-
- private static IntBuffer copyBuffer(IntBuffer ib) {
- if (ib != null) {
- int[] ia = new int [ ib.capacity() ];
- int p = ib.position();
- int l = ib.limit();
- System.arraycopy(ib.array(), 0, ia, 0, ia.length);
- return IntBuffer.wrap(ia, p, l - p);
- } else {
- return null;
- }
- }
-
- private static List copyAssociations(List ca) {
- if (ca != null) {
- return new ArrayList(ca);
- } else {
- return ca;
- }
- }
-
- /**
- * A structure class encapsulating an interval of characters
- * expressed as an offset and count of Unicode scalar values (in
- * an IntBuffer). A <code>CharAssociation</code> is used to
- * maintain a backpointer from a glyph to one or more character
- * intervals from which the glyph was derived.
- *
- * Each glyph in a glyph sequence is associated with a single
- * <code>CharAssociation</code> instance.
- *
- * A <code>CharAssociation</code> instance is additionally (and
- * optionally) used to record predication information about the
- * glyph, such as whether the glyph was produced by the
- * application of a specific substitution table or whether its
- * position was adjusted by a specific poisitioning table.
- */
- public static class CharAssociation implements Cloneable {
-
- // instance state
- private final int offset;
- private final int count;
- private final int[] subIntervals;
- private Map<String, Object> predications;
-
- // class state
- private static volatile Map<String, PredicationMerger> predicationMergers;
-
- interface PredicationMerger {
- Object merge(String key, Object v1, Object v2);
- }
-
- /**
- * Instantiate a character association.
- * @param offset into array of Unicode scalar values (in associated IntBuffer)
- * @param count of Unicode scalar values (in associated IntBuffer)
- * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
- * members of array are sub-interval starts, and odd members are sub-interval
- * ends (exclusive)
- */
- public CharAssociation(int offset, int count, int[] subIntervals) {
- this.offset = offset;
- this.count = count;
- this.subIntervals = ((subIntervals != null) && (subIntervals.length > 2)) ? subIntervals : null;
- }
-
- /**
- * Instantiate a non-disjoint character association.
- * @param offset into array of UTF-16 code elements (in associated CharSequence)
- * @param count of UTF-16 character code elements (in associated CharSequence)
- */
- public CharAssociation(int offset, int count) {
- this (offset, count, null);
- }
-
- /**
- * Instantiate a non-disjoint character association.
- * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
- * members of array are sub-interval starts, and odd members are sub-interval
- * ends (exclusive)
- */
- public CharAssociation(int[] subIntervals) {
- this (getSubIntervalsStart(subIntervals), getSubIntervalsLength(subIntervals), subIntervals);
- }
-
- /** @return offset (start of association interval) */
- public int getOffset() {
- return offset;
- }
-
- /** @return count (number of characer codes in association) */
- public int getCount() {
- return count;
- }
-
- /** @return start of association interval */
- public int getStart() {
- return getOffset();
- }
-
- /** @return end of association interval */
- public int getEnd() {
- return getOffset() + getCount();
- }
-
- /** @return true if association is disjoint */
- public boolean isDisjoint() {
- return subIntervals != null;
- }
-
- /** @return subintervals of disjoint association */
- public int[] getSubIntervals() {
- return subIntervals;
- }
-
- /** @return count of subintervals of disjoint association */
- public int getSubIntervalCount() {
- return (subIntervals != null) ? (subIntervals.length / 2) : 0;
- }
-
- /**
- * @param offset of interval in sequence
- * @param count length of interval
- * @return true if this association is contained within [offset,offset+count)
- */
- public boolean contained(int offset, int count) {
- int s = offset;
- int e = offset + count;
- if (!isDisjoint()) {
- int s0 = getStart();
- int e0 = getEnd();
- return (s0 >= s) && (e0 <= e);
- } else {
- int ns = getSubIntervalCount();
- for (int i = 0; i < ns; i++) {
- int s0 = subIntervals [ 2 * i + 0 ];
- int e0 = subIntervals [ 2 * i + 1 ];
- if ((s0 >= s) && (e0 <= e)) {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Set predication <KEY,VALUE>.
- * @param key predication key
- * @param value predication value
- */
- public void setPredication(String key, Object value) {
- if (predications == null) {
- predications = new HashMap<String, Object>();
- }
- if (predications != null) {
- predications.put(key, value);
- }
- }
-
- /**
- * Get predication KEY.
- * @param key predication key
- * @return predication KEY at OFFSET or null if none exists
- */
- public Object getPredication(String key) {
- if (predications != null) {
- return predications.get(key);
- } else {
- return null;
- }
- }
-
- /**
- * Merge predication <KEY,VALUE>.
- * @param key predication key
- * @param value predication value
- */
- public void mergePredication(String key, Object value) {
- if (predications == null) {
- predications = new HashMap<String, Object>();
- }
- if (predications != null) {
- if (predications.containsKey(key)) {
- Object v1 = predications.get(key);
- Object v2 = value;
- predications.put(key, mergePredicationValues(key, v1, v2));
- } else {
- predications.put(key, value);
- }
- }
- }
-
- /**
- * Merge predication values V1 and V2 on KEY. Uses registered <code>PredicationMerger</code>
- * if one exists, otherwise uses V2 if non-null, otherwise uses V1.
- * @param key predication key
- * @param v1 first (original) predication value
- * @param v2 second (to be merged) predication value
- * @return merged value
- */
- public static Object mergePredicationValues(String key, Object v1, Object v2) {
- PredicationMerger pm = getPredicationMerger(key);
- if (pm != null) {
- return pm.merge(key, v1, v2);
- } else if (v2 != null) {
- return v2;
- } else {
- return v1;
- }
- }
-
- /**
- * Merge predications from another CA.
- * @param ca from which to merge
- */
- public void mergePredications(CharAssociation ca) {
- if (ca.predications != null) {
- for (Map.Entry<String, Object> e : ca.predications.entrySet()) {
- mergePredication(e.getKey(), e.getValue());
- }
- }
- }
-
- /** {@inheritDoc} */
- public Object clone() {
- try {
- CharAssociation ca = (CharAssociation) super.clone();
- if (predications != null) {
- ca.predications = new HashMap<String, Object>(predications);
- }
- return ca;
- } catch (CloneNotSupportedException e) {
- return null;
- }
- }
-
- /**
- * Register predication merger PM for KEY.
- * @param key for predication merger
- * @param pm predication merger
- */
- public static void setPredicationMerger(String key, PredicationMerger pm) {
- if (predicationMergers == null) {
- predicationMergers = new HashMap<String, PredicationMerger>();
- }
- if (predicationMergers != null) {
- predicationMergers.put(key, pm);
- }
- }
-
- /**
- * Obtain predication merger for KEY.
- * @param key for predication merger
- * @return predication merger or null if none exists
- */
- public static PredicationMerger getPredicationMerger(String key) {
- if (predicationMergers != null) {
- return predicationMergers.get(key);
- } else {
- return null;
- }
- }
-
- /**
- * Replicate association to form <code>repeat</code> new associations.
- * @param a association to replicate
- * @param repeat count
- * @return array of replicated associations
- */
- public static CharAssociation[] replicate(CharAssociation a, int repeat) {
- CharAssociation[] aa = new CharAssociation [ repeat ];
- for (int i = 0, n = aa.length; i < n; i++) {
- aa [ i ] = (CharAssociation) a.clone();
- }
- return aa;
- }
-
- /**
- * Join (merge) multiple associations into a single, potentially disjoint
- * association.
- * @param aa array of associations to join
- * @return (possibly disjoint) association containing joined associations
- */
- public static CharAssociation join(CharAssociation[] aa) {
- CharAssociation ca;
- // extract sorted intervals
- int[] ia = extractIntervals(aa);
- if ((ia == null) || (ia.length == 0)) {
- ca = new CharAssociation(0, 0);
- } else if (ia.length == 2) {
- int s = ia[0];
- int e = ia[1];
- ca = new CharAssociation(s, e - s);
- } else {
- ca = new CharAssociation(mergeIntervals(ia));
- }
- return mergePredicates(ca, aa);
- }
-
- private static CharAssociation mergePredicates(CharAssociation ca, CharAssociation[] aa) {
- for (CharAssociation a : aa) {
- ca.mergePredications(a);
- }
- return ca;
- }
-
- private static int getSubIntervalsStart(int[] ia) {
- int us = Integer.MAX_VALUE;
- int ue = Integer.MIN_VALUE;
- if (ia != null) {
- for (int i = 0, n = ia.length; i < n; i += 2) {
- int s = ia [ i + 0 ];
- int e = ia [ i + 1 ];
- if (s < us) {
- us = s;
- }
- if (e > ue) {
- ue = e;
- }
- }
- if (ue < 0) {
- ue = 0;
- }
- if (us > ue) {
- us = ue;
- }
- }
- return us;
- }
-
- private static int getSubIntervalsLength(int[] ia) {
- int us = Integer.MAX_VALUE;
- int ue = Integer.MIN_VALUE;
- if (ia != null) {
- for (int i = 0, n = ia.length; i < n; i += 2) {
- int s = ia [ i + 0 ];
- int e = ia [ i + 1 ];
- if (s < us) {
- us = s;
- }
- if (e > ue) {
- ue = e;
- }
- }
- if (ue < 0) {
- ue = 0;
- }
- if (us > ue) {
- us = ue;
- }
- }
- return ue - us;
- }
-
- /**
- * Extract sorted sub-intervals.
- */
- private static int[] extractIntervals(CharAssociation[] aa) {
- int ni = 0;
- for (int i = 0, n = aa.length; i < n; i++) {
- CharAssociation a = aa [ i ];
- if (a.isDisjoint()) {
- ni += a.getSubIntervalCount();
- } else {
- ni += 1;
- }
- }
- int[] sa = new int [ ni ];
- int[] ea = new int [ ni ];
- for (int i = 0, k = 0; i < aa.length; i++) {
- CharAssociation a = aa [ i ];
- if (a.isDisjoint()) {
- int[] da = a.getSubIntervals();
- for (int j = 0; j < da.length; j += 2) {
- sa [ k ] = da [ j + 0 ];
- ea [ k ] = da [ j + 1 ];
- k++;
- }
- } else {
- sa [ k ] = a.getStart();
- ea [ k ] = a.getEnd();
- k++;
- }
- }
- return sortIntervals(sa, ea);
- }
-
- private static final int[] SORT_INCREMENTS_16
- = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 };
-
- private static final int[] SORT_INCREMENTS_03
- = { 7, 3, 1 };
-
- /**
- * Sort sub-intervals using modified Shell Sort.
- */
- private static int[] sortIntervals(int[] sa, int[] ea) {
- assert sa != null;
- assert ea != null;
- assert sa.length == ea.length;
- int ni = sa.length;
- int[] incr = (ni < 21) ? SORT_INCREMENTS_03 : SORT_INCREMENTS_16;
- for (int k = 0; k < incr.length; k++) {
- for (int h = incr [ k ], i = h, n = ni, j; i < n; i++) {
- int s1 = sa [ i ];
- int e1 = ea [ i ];
- for (j = i; j >= h; j -= h) {
- int s2 = sa [ j - h ];
- int e2 = ea [ j - h ];
- if (s2 > s1) {
- sa [ j ] = s2;
- ea [ j ] = e2;
- } else if ((s2 == s1) && (e2 > e1)) {
- sa [ j ] = s2;
- ea [ j ] = e2;
- } else {
- break;
- }
- }
- sa [ j ] = s1;
- ea [ j ] = e1;
- }
- }
- int[] ia = new int [ ni * 2 ];
- for (int i = 0; i < ni; i++) {
- ia [ (i * 2) + 0 ] = sa [ i ];
- ia [ (i * 2) + 1 ] = ea [ i ];
- }
- return ia;
- }
-
- /**
- * Merge overlapping and abutting sub-intervals.
- */
- private static int[] mergeIntervals(int[] ia) {
- int ni = ia.length;
- int i;
- int n;
- int nm;
- int is;
- int ie;
- // count merged sub-intervals
- for (i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2) {
- int s = ia [ i + 0 ];
- int e = ia [ i + 1 ];
- if ((ie < 0) || (s > ie)) {
- is = s;
- ie = e;
- nm++;
- } else if (s >= is) {
- if (e > ie) {
- ie = e;
- }
- }
- }
- int[] mi = new int [ nm * 2 ];
- // populate merged sub-intervals
- for (i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2) {
- int s = ia [ i + 0 ];
- int e = ia [ i + 1 ];
- int k = nm * 2;
- if ((ie < 0) || (s > ie)) {
- is = s;
- ie = e;
- mi [ k + 0 ] = is;
- mi [ k + 1 ] = ie;
- nm++;
- } else if (s >= is) {
- if (e > ie) {
- ie = e;
- }
- mi [ k - 1 ] = ie;
- }
- }
- return mi;
- }
-
- }
-
- }
|