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.schema.converter;
021
022
023import java.io.ByteArrayInputStream;
024import java.io.File;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.PipedInputStream;
028import java.io.PipedOutputStream;
029import java.nio.file.Files;
030import java.nio.file.Paths;
031import java.text.ParseException;
032import java.util.List;
033
034import org.apache.commons.lang.exception.ExceptionUtils;
035import org.apache.directory.api.i18n.I18n;
036import org.apache.directory.api.util.Strings;
037
038import antlr.RecognitionException;
039import antlr.TokenStreamException;
040
041
042/**
043 * A reusable wrapper for antlr generated schema parsers.
044 *
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 */
047public class SchemaParser
048{
049    /** The antlr generated parser */
050    private antlrSchemaConverterParser parser = null;
051
052    /** A pipe into the parser */
053    private PipedOutputStream parserIn = null;
054
055    /** A temporary buffer storing the read schema bytes */
056    private byte[] buf = new byte[128];
057
058    /** The inputStream mapped over the schema file to parse */
059    private InputStream schemaIn;
060
061    /** The thread used to read the schema */
062    private Thread producerThread;
063
064
065    /**
066     * Creates a reusable instance of an SchemaParser.
067     *
068     * @throws java.io.IOException if the pipe cannot be formed
069     */
070    public SchemaParser() throws IOException
071    {
072        init();
073    }
074
075
076    /**
077     * Initializes a parser and its plumbing.
078     *
079     * @throws java.io.IOException if a pipe cannot be formed.
080     */
081    public synchronized void init() throws IOException
082    {
083        parserIn = new PipedOutputStream();
084        PipedInputStream in = new PipedInputStream();
085        parserIn.connect( in );
086        antlrSchemaConverterLexer lexer = new antlrSchemaConverterLexer( in );
087        parser = new antlrSchemaConverterParser( lexer );
088    }
089
090
091    /**
092     * Clear the parser.
093     */
094    public synchronized void clear()
095    {
096        parser.clear();
097    }
098
099
100    /**
101     * Thread safe method parses an OpenLDAP schemaObject element/object.
102     *
103     * @param schemaObject the String image of a complete schema object
104     * @return The list of parsed schema elements
105     * @throws java.io.IOException If the schema file can't be processed
106     * @throws java.text.ParseException If we weren't able to parse the schema
107     */
108    public synchronized List<SchemaElement> parse( String schemaObject ) throws IOException, ParseException
109    {
110        if ( ( schemaObject == null ) || ( schemaObject.trim().equals( Strings.EMPTY_STRING ) ) )
111        {
112            throw new ParseException( I18n.err( I18n.ERR_06001_EMPTY_OR_NULL_SCHEMA_OBJECT ), 0 );
113        }
114
115        schemaIn = new ByteArrayInputStream( Strings.getBytesUtf8( schemaObject ) );
116
117        if ( producerThread == null )
118        {
119            producerThread = new Thread( new DataProducer() );
120        }
121
122        producerThread.start();
123        
124        return invokeParser( schemaObject );
125    }
126
127
128    /**
129     * Invoke the parser
130     * 
131     * @param schemaName The schema to be parsed
132     * @return A list of schema elements
133     * @throws java.io.IOException If the schema file can't be processed
134     * @throws java.text.ParseException If we weren't able to parse the schema
135     */
136    private List<SchemaElement> invokeParser( String schemaName ) throws IOException, ParseException
137    {
138        try
139        {
140            parser.parseSchema();
141
142            return parser.getSchemaElements();
143        }
144        catch ( RecognitionException re )
145        {
146            String msg = I18n.err( I18n.ERR_06002_PARSER_FAILURE, schemaName, ExceptionUtils.getFullStackTrace( re ) );
147            init();
148            throw new ParseException( msg, re.getColumn() );
149        }
150        catch ( TokenStreamException tse )
151        {
152            String msg = I18n.err( I18n.ERR_06002_PARSER_FAILURE, schemaName, ExceptionUtils.getFullStackTrace( tse ) );
153            init();
154            throw new ParseException( msg, 0 );
155        }
156    }
157
158
159    /**
160     * Thread safe method parses a stream of OpenLDAP schemaObject elements/objects.
161     *
162     * @param schemaIn a stream of schema objects
163     * @return A list of schema elements
164     * @throws java.io.IOException If the schema file can't be processed
165     * @throws java.text.ParseException If we weren't able to parse the schema
166     */
167    public synchronized List<SchemaElement> parse( InputStream schemaIn ) throws IOException, ParseException
168    {
169        this.schemaIn = schemaIn;
170
171        if ( producerThread == null )
172        {
173            producerThread = new Thread( new DataProducer() );
174        }
175
176        producerThread.start();
177
178        return invokeParser( "schema input stream ==> " + schemaIn.toString() );
179    }
180
181
182    /**
183     * Thread safe method parses a file of OpenLDAP schemaObject elements/objects.
184     *
185     * @param schemaFile a file of schema objects
186     * @throws java.io.IOException If the schema file can't be processed
187     * @throws java.text.ParseException If we weren't able to parse the schema
188     */
189    public synchronized void parse( File schemaFile ) throws IOException, ParseException
190    {
191        schemaIn = Files.newInputStream( Paths.get( schemaFile.getPath() ) );
192
193        if ( producerThread == null )
194        {
195            producerThread = new Thread( new DataProducer() );
196        }
197
198        producerThread.start();
199        invokeParser( "schema file ==> " + schemaFile.getAbsolutePath() );
200    }
201    
202
203    /**
204     * The thread which read the schema files and fill the
205     * temporary buffer used by the lexical analyzer.
206     */
207    private class DataProducer implements Runnable
208    {
209        /**
210         * {@inheritDoc}
211         */
212        @Override
213        public void run()
214        {
215            int count = -1;
216
217            try
218            {
219                while ( ( count = schemaIn.read( buf ) ) != -1 )
220                {
221                    parserIn.write( buf, 0, count );
222                    parserIn.flush();
223                }
224
225                // using an input termination token END - need extra space to return
226                parserIn.write( Strings.getBytesUtf8( "END " ) );
227            }
228            catch ( IOException e )
229            {
230                e.printStackTrace();
231            }
232        }
233    }
234}