blob: b515301bac30a15384b161b29e16d716ead6f325 [file] [log] [blame]
/*
* 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.fonts;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.fop.util.CharUtilities;
// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
// CSOFF: WhitespaceAfterCheck
// CSOFF: NoWhitespaceAfterCheck
/**
* 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.
* @author Glenn Adams
*/
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[] sortIncrements16 // CSOK: ConstantNameCheck
= { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 };
private static final int[] sortIncrements03 // CSOK: ConstantNameCheck
= { 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 ) ? sortIncrements03 : sortIncrements16;
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, n, nm, is, 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;
}
}
}