001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.api.ldap.model.schema.syntaxCheckers;
021
022
023import java.util.HashSet;
024import java.util.Set;
025
026import org.apache.directory.api.i18n.I18n;
027import org.apache.directory.api.ldap.model.constants.SchemaConstants;
028import org.apache.directory.api.ldap.model.schema.SyntaxChecker;
029import org.apache.directory.api.util.Chars;
030import org.apache.directory.api.util.Strings;
031
032
033/**
034 * A SyntaxChecker which verifies that a value is a delivery method 
035 * according to RFC 4517.
036 * 
037 * From RFC 4517 & RFC 4512:
038 * <pre>
039 * DeliveryMethod = pdm *( WSP DOLLAR WSP pdm )
040 *
041 * pdm = "any" | "mhs" | "physical" | "telex" | "teletex" |
042 *       "g3fax" | "g4fax" | "ia5" | "videotex" | "telephone"
043 *           
044 * WSP     = 0*SPACE  ; zero or more " "
045 * DOLLAR  = %x24 ; dollar sign ("$")
046 * SPACE   = %x20 ; space (" ")
047 * </pre>
048 *
049 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
050 */
051@SuppressWarnings("serial")
052public final class DeliveryMethodSyntaxChecker extends SyntaxChecker
053{
054    private static final String[] PDMS =
055        {
056            "any", "mhs", "physical", "telex", "teletex",
057            "g3fax", "g4fax", "ia5", "videotex", "telephone"
058        };
059
060    /** The Set which contains the delivery methods */
061    private static final Set<String> DELIVERY_METHODS = new HashSet<>();
062
063    /** Initialization of the delivery methods set */
064    static
065    {
066        for ( String country : PDMS )
067        {
068            DELIVERY_METHODS.add( country );
069        }
070    }
071    
072    /**
073     * A static instance of DeliveryMethodSyntaxChecker
074     */
075    public static final DeliveryMethodSyntaxChecker INSTANCE = 
076        new DeliveryMethodSyntaxChecker( SchemaConstants.DELIVERY_METHOD_SYNTAX );
077    
078    /**
079     * A static Builder for this class
080     */
081    public static final class Builder extends SCBuilder<DeliveryMethodSyntaxChecker>
082    {
083        /**
084         * The Builder constructor
085         */
086        private Builder()
087        {
088            super( SchemaConstants.DELIVERY_METHOD_SYNTAX );
089        }
090        
091        
092        /**
093         * Create a new instance of DeliveryMethodSyntaxChecker
094         * @return A new instance of DeliveryMethodSyntaxChecker
095         */
096        @Override
097        public DeliveryMethodSyntaxChecker build()
098        {
099            return new DeliveryMethodSyntaxChecker( oid );
100        }
101    }
102
103
104    /**
105     * Creates a new instance of DeliveryMethodSyntaxChecker.
106     *
107     * @param oid The OID to use for this SyntaxChecker
108     */
109    private DeliveryMethodSyntaxChecker( String oid )
110    {
111        super( oid );
112    }
113
114    
115    /**
116     * @return An instance of the Builder for this class
117     */
118    public static Builder builder()
119    {
120        return new Builder();
121    }
122
123
124    /**
125     * 
126     * Check if the string contains a delivery method which has 
127     * not already been found.
128     * 
129     * @param strValue The string we want to look into for a PDM 
130     * @param pos The current position in the string
131     * @param pdms The set containing all the PDM
132     * @return if a Prefered Delivery Method is found in the given string, returns 
133     * its position, otherwise, returns -1
134     */
135    private int isPdm( String strValue, int start, Set<String> pdms )
136    {
137        int pos = start;
138
139        while ( Chars.isAlphaDigit( strValue, pos ) )
140        {
141            pos++;
142        }
143
144        // No ascii string, this is not a delivery method
145        if ( pos == start )
146        {
147            return -1;
148        }
149
150        String pdm = strValue.substring( start, pos );
151
152        if ( !DELIVERY_METHODS.contains( pdm ) )
153        {
154            // The delivery method is unknown
155            return -1;
156        }
157        else
158        {
159            if ( pdms.contains( pdm ) )
160            {
161                // The delivery method has already been found
162                return -1;
163            }
164            else
165            {
166                pdms.add( pdm );
167                return pos;
168            }
169        }
170    }
171
172
173    /**
174     * {@inheritDoc}
175     */
176    @Override
177    public boolean isValidSyntax( Object value )
178    {
179        String strValue;
180
181        if ( value == null )
182        {
183            if ( LOG.isDebugEnabled() )
184            {
185                LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, "null" ) );
186            }
187            
188            return false;
189        }
190
191        if ( value instanceof String )
192        {
193            strValue = ( String ) value;
194        }
195        else if ( value instanceof byte[] )
196        {
197            strValue = Strings.utf8ToString( ( byte[] ) value );
198        }
199        else
200        {
201            strValue = value.toString();
202        }
203
204        if ( strValue.length() == 0 )
205        {
206            if ( LOG.isDebugEnabled() )
207            {
208                LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) );
209            }
210            
211            return false;
212        }
213
214        // We will get the first delivery method
215        int length = strValue.length();
216        int pos = 0;
217        Set<String> pmds = new HashSet<>();
218
219        pos = isPdm( strValue, pos, pmds );
220        
221        if ( pos == -1 )
222        {
223            if ( LOG.isDebugEnabled() )
224            {
225                LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) );
226            }
227            
228            return false;
229        }
230
231        // We have found at least the first pmd,
232        // now iterate through the other ones. We may have
233        // SP* '$' SP* before each pmd.
234        while ( pos < length )
235        {
236            // Skip spaces
237            while ( Strings.isCharASCII( strValue, pos, ' ' ) )
238            {
239                pos++;
240            }
241
242            if ( !Strings.isCharASCII( strValue, pos, '$' ) )
243            {
244                // A '$' was expected
245                if ( LOG.isDebugEnabled() )
246                {
247                    LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) );
248                }
249                
250                return false;
251            }
252            else
253            {
254                pos++;
255            }
256
257            // Skip spaces
258            while ( Strings.isCharASCII( strValue, pos, ' ' ) )
259            {
260                pos++;
261            }
262
263            pos = isPdm( strValue, pos, pmds );
264            
265            if ( pos == -1 )
266            {
267                if ( LOG.isDebugEnabled() )
268                {
269                    LOG.debug( I18n.err( I18n.ERR_04488_SYNTAX_INVALID, value ) );
270                }
271                
272                return false;
273            }
274        }
275
276        if ( LOG.isDebugEnabled() )
277        {
278            LOG.debug( I18n.msg( I18n.MSG_04489_SYNTAX_VALID, value ) );
279        }
280        
281        return true;
282    }
283}