1865f88afc0d59d886fb2ad50429e584ecf17fa81Brian/* 2865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Copyright 2001-2004 The Apache Software Foundation. 3865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 4865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Licensed under the Apache License, Version 2.0 (the "License"); 5865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * you may not use this file except in compliance with the License. 6865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * You may obtain a copy of the License at 7865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 8865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * http://www.apache.org/licenses/LICENSE-2.0 9865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 10865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Unless required by applicable law or agreed to in writing, software 11865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * distributed under the License is distributed on an "AS IS" BASIS, 12865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * See the License for the specific language governing permissions and 14865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * limitations under the License. 15865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 16865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 17865f88afc0d59d886fb2ad50429e584ecf17fa81Brianpackage org.apache.commons.codec.language; 18865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 19865f88afc0d59d886fb2ad50429e584ecf17fa81Brianimport org.apache.commons.codec.EncoderException; 20865f88afc0d59d886fb2ad50429e584ecf17fa81Brianimport org.apache.commons.codec.StringEncoder; 21865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 22865f88afc0d59d886fb2ad50429e584ecf17fa81Brian/** 23865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Encodes a string into a Soundex value. Soundex is an encoding used to relate similar names, but can also be used as a 24865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * general purpose scheme to find word with similar phonemes. 25c223c6b663cd5db39ba19c2be74b88cc3b8f53f3Brian * 26c223c6b663cd5db39ba19c2be74b88cc3b8f53f3Brian * @author Apache Software Foundation 27c223c6b663cd5db39ba19c2be74b88cc3b8f53f3Brian * @version $Id: Soundex.java,v 1.26 2004/07/07 23:15:24 ggregory Exp $ 28c223c6b663cd5db39ba19c2be74b88cc3b8f53f3Brian * 29c223c6b663cd5db39ba19c2be74b88cc3b8f53f3Brian * @deprecated Please use {@link java.net.URL#openConnection} instead. 30865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a> 31c968d3d410a1897ecbb41d3557adaef69a4c627aBrian * for further details. 32865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 33865f88afc0d59d886fb2ad50429e584ecf17fa81Brian@Deprecated 34865f88afc0d59d886fb2ad50429e584ecf17fa81Brianpublic class Soundex implements StringEncoder { 35865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 36865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 37865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * An instance of Soundex using the US_ENGLISH_MAPPING mapping. 38865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 39865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @see #US_ENGLISH_MAPPING 40865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 41865f88afc0d59d886fb2ad50429e584ecf17fa81Brian public static final Soundex US_ENGLISH = new Soundex(); 42865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 43865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 44865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * This is a default mapping of the 26 letters used in US English. A value of <code>0</code> for a letter position 45865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * means do not encode. 46865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * <p> 47865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * (This constant is provided as both an implementation convenience and to allow Javadoc to pick 48865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * up the value for the constant values page.) 49865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * </p> 50865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 51865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @see #US_ENGLISH_MAPPING 52865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 53865f88afc0d59d886fb2ad50429e584ecf17fa81Brian public static final String US_ENGLISH_MAPPING_STRING = "01230120022455012623010202"; 54865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 55865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 56865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * This is a default mapping of the 26 letters used in US English. A value of <code>0</code> for a letter position 57865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * means do not encode. 58865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 59865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @see Soundex#Soundex(char[]) 60865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 61865f88afc0d59d886fb2ad50429e584ecf17fa81Brian public static final char[] US_ENGLISH_MAPPING = US_ENGLISH_MAPPING_STRING.toCharArray(); 62865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 63865f88afc0d59d886fb2ad50429e584ecf17fa81Brian // BEGIN android-note 64865f88afc0d59d886fb2ad50429e584ecf17fa81Brian // Removed @see reference to SoundexUtils below, since the class isn't 65865f88afc0d59d886fb2ad50429e584ecf17fa81Brian // public. 66865f88afc0d59d886fb2ad50429e584ecf17fa81Brian // END android-note 67865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 68865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Encodes the Strings and returns the number of characters in the two encoded Strings that are the same. This 69865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * return value ranges from 0 through 4: 0 indicates little or no similarity, and 4 indicates strong similarity or 70865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * identical values. 71865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 72865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @param s1 73865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * A String that will be encoded and compared. 74865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @param s2 75865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * A String that will be encoded and compared. 76865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @return The number of characters in the two encoded Strings that are the same from 0 to 4. 77865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 78865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @see <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_de-dz_8co5.asp"> MS 79865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * T-SQL DIFFERENCE </a> 80865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 81865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @throws EncoderException 82865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * if an error occurs encoding one of the strings 83865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @since 1.3 84865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 85865f88afc0d59d886fb2ad50429e584ecf17fa81Brian public int difference(String s1, String s2) throws EncoderException { 86865f88afc0d59d886fb2ad50429e584ecf17fa81Brian return SoundexUtils.difference(this, s1, s2); 87865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 88865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 89865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 90865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * The maximum length of a Soundex code - Soundex codes are only four characters by definition. 91865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 92865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @deprecated This feature is not needed since the encoding size must be constant. Will be removed in 2.0. 93865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 94865f88afc0d59d886fb2ad50429e584ecf17fa81Brian private int maxLength = 4; 95865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 96865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 97865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Every letter of the alphabet is "mapped" to a numerical value. This char array holds the values to which each 98865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * letter is mapped. This implementation contains a default map for US_ENGLISH 99f6803de7396edda2223adf7ff7445579dbe475c9Brian */ 100f6803de7396edda2223adf7ff7445579dbe475c9Brian private char[] soundexMapping; 101f6803de7396edda2223adf7ff7445579dbe475c9Brian 102865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 103ced6f76404ff1a6713c85edff17551f82c33cc24Brian * Creates an instance using US_ENGLISH_MAPPING 104865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 105865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @see Soundex#Soundex(char[]) 1063ed1acd13c7876288a5d1ab6d288b1654f0c2e6dBrian * @see Soundex#US_ENGLISH_MAPPING 107865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 108865f88afc0d59d886fb2ad50429e584ecf17fa81Brian public Soundex() { 109f3e507ef9f75dbfc58ccd07b5fe8cfca10d9a9e3Brian this(US_ENGLISH_MAPPING); 110f3e507ef9f75dbfc58ccd07b5fe8cfca10d9a9e3Brian } 11110b5895597d5e069183cb647d17eb412effceb4fBrian 11260d136f63c5a5a18b12952ec8e8532cbce086a4dBrian /** 11360d136f63c5a5a18b12952ec8e8532cbce086a4dBrian * Creates a soundex instance using the given mapping. This constructor can be used to provide an internationalized 11460d136f63c5a5a18b12952ec8e8532cbce086a4dBrian * mapping for a non-Western character set. 11560d136f63c5a5a18b12952ec8e8532cbce086a4dBrian * 116e48f0b09abe42aa3393a492af07e53b76ad0ff3cBrian * Every letter of the alphabet is "mapped" to a numerical value. This char array holds the values to which each 117af0ae93863b4c876e70efa4e7406f04a3409f135Brian * letter is mapped. This implementation contains a default map for US_ENGLISH 118af0ae93863b4c876e70efa4e7406f04a3409f135Brian * 119af0ae93863b4c876e70efa4e7406f04a3409f135Brian * @param mapping 120af0ae93863b4c876e70efa4e7406f04a3409f135Brian * Mapping array to use when finding the corresponding code for a given character 12110b5895597d5e069183cb647d17eb412effceb4fBrian */ 122f6803de7396edda2223adf7ff7445579dbe475c9Brian public Soundex(char[] mapping) { 123865f88afc0d59d886fb2ad50429e584ecf17fa81Brian this.setSoundexMapping(mapping); 124865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 125865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 126865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 127865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Encodes an Object using the soundex algorithm. This method is provided in order to satisfy the requirements of 128865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * the Encoder interface, and will throw an EncoderException if the supplied object is not of type java.lang.String. 129865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 130865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @param pObject 131865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Object to encode 132f6803de7396edda2223adf7ff7445579dbe475c9Brian * @return An object (or type java.lang.String) containing the soundex code which corresponds to the String 133f6803de7396edda2223adf7ff7445579dbe475c9Brian * supplied. 134f6803de7396edda2223adf7ff7445579dbe475c9Brian * @throws EncoderException 135865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * if the parameter supplied is not of type java.lang.String 136865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @throws IllegalArgumentException 137865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * if a character is not mapped 138865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 139865f88afc0d59d886fb2ad50429e584ecf17fa81Brian public Object encode(Object pObject) throws EncoderException { 140865f88afc0d59d886fb2ad50429e584ecf17fa81Brian if (!(pObject instanceof String)) { 141865f88afc0d59d886fb2ad50429e584ecf17fa81Brian throw new EncoderException("Parameter supplied to Soundex encode is not of type java.lang.String"); 142865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 143865f88afc0d59d886fb2ad50429e584ecf17fa81Brian return soundex((String) pObject); 1441c09bcfdda4083636a3ac27d804a34ef87875ce7Brian } 145865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 1461c09bcfdda4083636a3ac27d804a34ef87875ce7Brian /** 1478b5fce6bcc88cd9dd321f0db95c1714e5e5e85a1Brian * Encodes a String using the soundex algorithm. 148865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 149865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @param pString 150865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * A String object to encode 151865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @return A Soundex code corresponding to the String supplied 1528b5fce6bcc88cd9dd321f0db95c1714e5e5e85a1Brian * @throws IllegalArgumentException 153865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * if a character is not mapped 1548b5fce6bcc88cd9dd321f0db95c1714e5e5e85a1Brian */ 1551c09bcfdda4083636a3ac27d804a34ef87875ce7Brian public String encode(String pString) { 156865f88afc0d59d886fb2ad50429e584ecf17fa81Brian return soundex(pString); 1571c09bcfdda4083636a3ac27d804a34ef87875ce7Brian } 1581c09bcfdda4083636a3ac27d804a34ef87875ce7Brian 1598b5fce6bcc88cd9dd321f0db95c1714e5e5e85a1Brian /** 1601c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * Used internally by the SoundEx algorithm. 1611c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * 1621c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * Consonants from the same code group separated by W or H are treated as one. 1631c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * 1641c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * @param str 1651c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * the cleaned working string to encode (in upper case). 1661c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * @param index 1671c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * the character position to encode 1681c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * @return Mapping code for a particular character 1691c09bcfdda4083636a3ac27d804a34ef87875ce7Brian * @throws IllegalArgumentException 1708b5fce6bcc88cd9dd321f0db95c1714e5e5e85a1Brian * if the character is not mapped 1711c09bcfdda4083636a3ac27d804a34ef87875ce7Brian */ 1721c09bcfdda4083636a3ac27d804a34ef87875ce7Brian private char getMappingCode(String str, int index) { 1731c09bcfdda4083636a3ac27d804a34ef87875ce7Brian char mappedChar = this.map(str.charAt(index)); 174865f88afc0d59d886fb2ad50429e584ecf17fa81Brian // HW rule check 175865f88afc0d59d886fb2ad50429e584ecf17fa81Brian if (index > 1 && mappedChar != '0') { 1761c09bcfdda4083636a3ac27d804a34ef87875ce7Brian char hwChar = str.charAt(index - 1); 1778b5fce6bcc88cd9dd321f0db95c1714e5e5e85a1Brian if ('H' == hwChar || 'W' == hwChar) { 178865f88afc0d59d886fb2ad50429e584ecf17fa81Brian char preHWChar = str.charAt(index - 2); 179865f88afc0d59d886fb2ad50429e584ecf17fa81Brian char firstCode = this.map(preHWChar); 180865f88afc0d59d886fb2ad50429e584ecf17fa81Brian if (firstCode == mappedChar || 'H' == preHWChar || 'W' == preHWChar) { 181865f88afc0d59d886fb2ad50429e584ecf17fa81Brian return 0; 182865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 183865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 184865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 185865f88afc0d59d886fb2ad50429e584ecf17fa81Brian return mappedChar; 186865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 187865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 188865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 189865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Returns the maxLength. Standard Soundex 190865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 191865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @deprecated This feature is not needed since the encoding size must be constant. Will be removed in 2.0. 192865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @return int 193865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 194865f88afc0d59d886fb2ad50429e584ecf17fa81Brian public int getMaxLength() { 195865f88afc0d59d886fb2ad50429e584ecf17fa81Brian return this.maxLength; 196865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 197865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 198865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 199865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Returns the soundex mapping. 200865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 201865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @return soundexMapping. 202865f88afc0d59d886fb2ad50429e584ecf17fa81Brian */ 203865f88afc0d59d886fb2ad50429e584ecf17fa81Brian private char[] getSoundexMapping() { 204865f88afc0d59d886fb2ad50429e584ecf17fa81Brian return this.soundexMapping; 205865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 20617ad1d12ebf04ebf4b2b35c1c37d36bb4d2bb550Brian 20717ad1d12ebf04ebf4b2b35c1c37d36bb4d2bb550Brian /** 20817ad1d12ebf04ebf4b2b35c1c37d36bb4d2bb550Brian * Maps the given upper-case character to it's Soudex code. 209865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 210865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @param ch 211865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * An upper-case character. 212865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @return A Soundex code. 213865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * @throws IllegalArgumentException 21417ad1d12ebf04ebf4b2b35c1c37d36bb4d2bb550Brian * Thrown if <code>ch</code> is not mapped. 21517ad1d12ebf04ebf4b2b35c1c37d36bb4d2bb550Brian */ 21617ad1d12ebf04ebf4b2b35c1c37d36bb4d2bb550Brian private char map(char ch) { 21717ad1d12ebf04ebf4b2b35c1c37d36bb4d2bb550Brian int index = ch - 'A'; 21817ad1d12ebf04ebf4b2b35c1c37d36bb4d2bb550Brian if (index < 0 || index >= this.getSoundexMapping().length) { 219865f88afc0d59d886fb2ad50429e584ecf17fa81Brian throw new IllegalArgumentException("The character is not mapped: " + ch); 220865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 221865f88afc0d59d886fb2ad50429e584ecf17fa81Brian return this.getSoundexMapping()[index]; 222865f88afc0d59d886fb2ad50429e584ecf17fa81Brian } 223865f88afc0d59d886fb2ad50429e584ecf17fa81Brian 224865f88afc0d59d886fb2ad50429e584ecf17fa81Brian /** 225865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * Sets the maxLength. 226865f88afc0d59d886fb2ad50429e584ecf17fa81Brian * 227 * @deprecated This feature is not needed since the encoding size must be constant. Will be removed in 2.0. 228 * @param maxLength 229 * The maxLength to set 230 */ 231 public void setMaxLength(int maxLength) { 232 this.maxLength = maxLength; 233 } 234 235 /** 236 * Sets the soundexMapping. 237 * 238 * @param soundexMapping 239 * The soundexMapping to set. 240 */ 241 private void setSoundexMapping(char[] soundexMapping) { 242 this.soundexMapping = soundexMapping; 243 } 244 245 /** 246 * Retreives the Soundex code for a given String object. 247 * 248 * @param str 249 * String to encode using the Soundex algorithm 250 * @return A soundex code for the String supplied 251 * @throws IllegalArgumentException 252 * if a character is not mapped 253 */ 254 public String soundex(String str) { 255 if (str == null) { 256 return null; 257 } 258 str = SoundexUtils.clean(str); 259 if (str.length() == 0) { 260 return str; 261 } 262 char out[] = {'0', '0', '0', '0'}; 263 char last, mapped; 264 int incount = 1, count = 1; 265 out[0] = str.charAt(0); 266 last = getMappingCode(str, 0); 267 while ((incount < str.length()) && (count < out.length)) { 268 mapped = getMappingCode(str, incount++); 269 if (mapped != 0) { 270 if ((mapped != '0') && (mapped != last)) { 271 out[count++] = mapped; 272 } 273 last = mapped; 274 } 275 } 276 return new String(out); 277 } 278 279} 280