001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.schema;
028    
029    
030    
031    import org.opends.server.admin.std.server.EqualityMatchingRuleCfg;
032    import org.opends.server.api.EqualityMatchingRule;
033    import org.opends.server.config.ConfigException;
034    import org.opends.server.protocols.asn1.ASN1OctetString;
035    import org.opends.server.types.ByteString;
036    import org.opends.server.types.DirectoryException;
037    import org.opends.server.types.InitializationException;
038    
039    import static org.opends.server.schema.SchemaConstants.*;
040    import static org.opends.server.util.StaticUtils.*;
041    
042    
043    
044    /**
045     * This class defines the caseIgnoreMatch matching rule defined in X.520 and
046     * referenced in RFC 2252.
047     */
048    public class CaseIgnoreEqualityMatchingRule
049           extends EqualityMatchingRule
050    {
051      /**
052       * Creates a new instance of this caseIgnoreMatch matching rule.
053       */
054      public CaseIgnoreEqualityMatchingRule()
055      {
056        super();
057      }
058    
059    
060    
061      /**
062       * {@inheritDoc}
063       */
064      public void initializeMatchingRule(EqualityMatchingRuleCfg configuration)
065             throws ConfigException, InitializationException
066      {
067        // No initialization is required.
068      }
069    
070    
071    
072      /**
073       * Retrieves the common name for this matching rule.
074       *
075       * @return  The common name for this matching rule, or <CODE>null</CODE> if
076       * it does not have a name.
077       */
078      public String getName()
079      {
080        return EMR_CASE_IGNORE_NAME;
081      }
082    
083    
084    
085      /**
086       * Retrieves the OID for this matching rule.
087       *
088       * @return  The OID for this matching rule.
089       */
090      public String getOID()
091      {
092        return EMR_CASE_IGNORE_OID;
093      }
094    
095    
096    
097      /**
098       * Retrieves the description for this matching rule.
099       *
100       * @return  The description for this matching rule, or <CODE>null</CODE> if
101       *          there is none.
102       */
103      public String getDescription()
104      {
105        // There is no standard description for this matching rule.
106        return null;
107      }
108    
109    
110    
111      /**
112       * Retrieves the OID of the syntax with which this matching rule is
113       * associated.
114       *
115       * @return  The OID of the syntax with which this matching rule is associated.
116       */
117      public String getSyntaxOID()
118      {
119        return SYNTAX_DIRECTORY_STRING_OID;
120      }
121    
122    
123    
124      /**
125       * Retrieves the normalized form of the provided value, which is best suited
126       * for efficiently performing matching operations on that value.
127       *
128       * @param  value  The value to be normalized.
129       *
130       * @return  The normalized version of the provided value.
131       *
132       * @throws  DirectoryException  If the provided value is invalid according to
133       *                              the associated attribute syntax.
134       */
135      public ByteString normalizeValue(ByteString value)
136             throws DirectoryException
137      {
138        byte[]        valueBytes  = value.value();
139        int           valueLength = valueBytes.length;
140    
141        // Find the first non-space character.
142        int startPos = 0;
143        while ((startPos < valueLength) && (valueBytes[startPos] == ' '))
144        {
145          startPos++;
146        }
147    
148        if (startPos == valueLength)
149        {
150          // This should only happen if the value is composed entirely of spaces.
151          // In that case, the normalized value is a single space.
152          return new ASN1OctetString(" ");
153        }
154    
155    
156        // Find the last non-space character;
157        int endPos = (valueLength-1);
158        while ((endPos > startPos) && (valueBytes[endPos] == ' '))
159        {
160          endPos--;
161        }
162    
163    
164        // Assume that the value contains only ASCII characters and iterate through
165        // it a character at a time, converting uppercase letters to lowercase.  If
166        // we find a non-ASCII character, then fall back on a more correct method.
167        StringBuilder buffer = new StringBuilder(endPos-startPos+1);
168        boolean lastWasSpace = false;
169        for (int i=startPos; i <= endPos; i++)
170        {
171          byte b = valueBytes[i];
172          if ((b & 0x7F) != b)
173          {
174            return normalizeNonASCII(value);
175          }
176    
177          switch (b)
178          {
179            case ' ':
180              if (! lastWasSpace)
181              {
182                buffer.append(' ');
183                lastWasSpace = true;
184              }
185              break;
186            case 'A':
187              buffer.append('a');
188              lastWasSpace = false;
189              break;
190            case 'B':
191              buffer.append('b');
192              lastWasSpace = false;
193              break;
194            case 'C':
195              buffer.append('c');
196              lastWasSpace = false;
197              break;
198            case 'D':
199              buffer.append('d');
200              lastWasSpace = false;
201              break;
202            case 'E':
203              buffer.append('e');
204              lastWasSpace = false;
205              break;
206            case 'F':
207              buffer.append('f');
208              lastWasSpace = false;
209              break;
210            case 'G':
211              buffer.append('g');
212              lastWasSpace = false;
213              break;
214            case 'H':
215              buffer.append('h');
216              lastWasSpace = false;
217              break;
218            case 'I':
219              buffer.append('i');
220              lastWasSpace = false;
221              break;
222            case 'J':
223              buffer.append('j');
224              lastWasSpace = false;
225              break;
226            case 'K':
227              buffer.append('k');
228              lastWasSpace = false;
229              break;
230            case 'L':
231              buffer.append('l');
232              lastWasSpace = false;
233              break;
234            case 'M':
235              buffer.append('m');
236              lastWasSpace = false;
237              break;
238            case 'N':
239              buffer.append('n');
240              lastWasSpace = false;
241              break;
242            case 'O':
243              buffer.append('o');
244              lastWasSpace = false;
245              break;
246            case 'P':
247              buffer.append('p');
248              lastWasSpace = false;
249              break;
250            case 'Q':
251              buffer.append('q');
252              lastWasSpace = false;
253              break;
254            case 'R':
255              buffer.append('r');
256              lastWasSpace = false;
257              break;
258            case 'S':
259              buffer.append('s');
260              lastWasSpace = false;
261              break;
262            case 'T':
263              buffer.append('t');
264              lastWasSpace = false;
265              break;
266            case 'U':
267              buffer.append('u');
268              lastWasSpace = false;
269              break;
270            case 'V':
271              buffer.append('v');
272              lastWasSpace = false;
273              break;
274            case 'W':
275              buffer.append('w');
276              lastWasSpace = false;
277              break;
278            case 'X':
279              buffer.append('x');
280              lastWasSpace = false;
281              break;
282            case 'Y':
283              buffer.append('y');
284              lastWasSpace = false;
285              break;
286            case 'Z':
287              buffer.append('z');
288              lastWasSpace = false;
289              break;
290            default:
291              buffer.append((char) b);
292              lastWasSpace = false;
293              break;
294          }
295        }
296    
297    
298        return new ASN1OctetString(buffer.toString());
299      }
300    
301    
302    
303      /**
304       * Normalizes a value that contains a non-ASCII string.
305       *
306       * @param  value  The non-ASCII value to normalize.
307       *
308       * @return  The normalized form of the provided value.
309       */
310      private ByteString normalizeNonASCII(ByteString value)
311      {
312        StringBuilder buffer = new StringBuilder();
313        toLowerCase(value.value(), buffer, true);
314    
315        int bufferLength = buffer.length();
316        if (bufferLength == 0)
317        {
318          if (value.value().length > 0)
319          {
320            // This should only happen if the value is composed entirely of spaces.
321            // In that case, the normalized value is a single space.
322            return new ASN1OctetString(" ");
323          }
324          else
325          {
326            // The value is empty, so it is already normalized.
327            return new ASN1OctetString();
328          }
329        }
330    
331    
332        // Replace any consecutive spaces with a single space.
333        for (int pos = bufferLength-1; pos > 0; pos--)
334        {
335          if (buffer.charAt(pos) == ' ')
336          {
337            if (buffer.charAt(pos-1) == ' ')
338            {
339              buffer.delete(pos, pos+1);
340            }
341          }
342        }
343    
344        return new ASN1OctetString(buffer.toString());
345      }
346    
347    
348    
349      /**
350       * Indicates whether the two provided normalized values are equal to each
351       * other.
352       *
353       * @param  value1  The normalized form of the first value to compare.
354       * @param  value2  The normalized form of the second value to compare.
355       *
356       * @return  <CODE>true</CODE> if the provided values are equal, or
357       *          <CODE>false</CODE> if not.
358       */
359      public boolean areEqual(ByteString value1, ByteString value2)
360      {
361        byte[] b1 = value1.value();
362        byte[] b2 = value2.value();
363    
364        int length = b1.length;
365        if (b2.length != length)
366        {
367          return false;
368        }
369    
370        for (int i=0; i < length; i++)
371        {
372          if (b1[i] != b2[i])
373          {
374            return false;
375          }
376        }
377    
378        return true;
379      }
380    }
381