View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.sequencer.text;
25  
26  import java.io.BufferedReader;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.InputStreamReader;
30  import net.jcip.annotations.ThreadSafe;
31  import org.modeshape.common.util.Logger;
32  import org.modeshape.graph.sequencer.SequencerOutput;
33  import org.modeshape.graph.sequencer.StreamSequencer;
34  import org.modeshape.graph.sequencer.StreamSequencerContext;
35  
36  /**
37   * The base class for the text sequencers. This class treats the text to be sequenced as a series of rows, with each row delimited
38   * by a line terminator. Concrete subclasses provide their own mechanisms for splitting a row of data into a series of columns.
39   * <p>
40   * This class provides some fundamental capabilities, including the ability to set a {@link #setCommentMarker(String) comment
41   * marker}, {@link #setMaximumLinesToRead(int) limit the number of lines} to be read from a file, and
42   * {@link #setRowFactoryClassName(String) provide custom transformations} from the sets of columns to the graph structure.
43   * </p>
44   */
45  @ThreadSafe
46  public abstract class AbstractTextSequencer implements StreamSequencer {
47  
48      private final Logger logger = Logger.getLogger(getClass());
49      private String rowFactoryClassName = null;
50      private String commentMarker = null;
51      private int maximumLinesToRead = -1;
52  
53      /**
54       * {@inheritDoc}
55       */
56      public void sequence( InputStream stream,
57                            SequencerOutput output,
58                            StreamSequencerContext context ) {
59          BufferedReader reader = null;
60          String line = null;
61          RowFactory rowFactory = null;
62          String rowFactoryClassName = this.rowFactoryClassName;
63          String commentMarker = this.commentMarker;
64          int maxLinesToRead = this.maximumLinesToRead;
65  
66          int rowCount = 0;
67  
68          try {
69              rowFactory = createRowFactory(rowFactoryClassName);
70          } catch (Exception ex) {
71              // This really shouldn't be able to happen, as we test-instantiate the class before it is set
72              throw new IllegalStateException(TextSequencerI18n.couldNotInstantiateRowFactory.text(rowFactoryClassName));
73          }
74  
75          try {
76              reader = new BufferedReader(new InputStreamReader(stream));
77  
78              while ((line = reader.readLine()) != null) {
79                  if (commentMarker != null && line.startsWith(this.commentMarker)) continue;
80                  if ((maxLinesToRead > 0) && (++rowCount > maxLinesToRead)) return;
81                  String[] columns = parseLine(line);
82  
83                  rowFactory.recordRow(context, output, columns);
84              }
85  
86          } catch (IOException ioe) {
87              logger.error(ioe, TextSequencerI18n.errorReadingLine, context.getInputPath());
88  
89          } finally {
90              try {
91                  if (reader != null) reader.close();
92              } catch (Exception ignore) {
93              }
94          }
95  
96      }
97  
98      /**
99       * Sets the comment marker to use. Any line that begins with the comment marker will be ignored and will not be counted as a
100      * read line for the purposes of the {@link #getMaximumLinesToRead() maximum line limitation}.
101      * 
102      * @param commentMarker the string that indicates that the line is a comment and should be ignored; null indicates that there
103      *        is no comment marker
104      */
105     public void setCommentMarker( String commentMarker ) {
106         this.commentMarker = commentMarker;
107     }
108 
109     /**
110      * @return the current comment marker; may be null
111      */
112     public String getCommentMarker() {
113         return commentMarker;
114     }
115 
116     /**
117      * @return the maximum number of lines to read when sequencing; non-positive numbers indicate that all lines should be read
118      *         and sequenced
119      */
120     public int getMaximumLinesToRead() {
121         return maximumLinesToRead;
122     }
123 
124     /**
125      * Sets the maximum number of lines to read. When this number is reached during the sequencing of any particular stream, the
126      * stream will be closed and remaining lines (if any) will be ignored. {@link #setCommentMarker(String) Comment lines} do not
127      * count towards the number of lines read.
128      * 
129      * @param maximumLinesToRead the maximum number of lines to read; a non-positive number indicates that all lines should be
130      *        read and sequenced.
131      */
132     public void setMaximumLinesToRead( int maximumLinesToRead ) {
133         this.maximumLinesToRead = maximumLinesToRead;
134     }
135 
136     /**
137      * @return the current row factory class name; may not be null
138      */
139     public String getRowFactoryClassName() {
140         return rowFactoryClassName;
141     }
142 
143     /**
144      * Sets the custom row factory class name. This method attempts to instantiate an instance of the custom {@link RowFactory}
145      * class prior to modifying the row factory class name to ensure that the new value represents a valid implementation.
146      * 
147      * @param rowFactoryClassName the fully-qualified class name of the new custom row factory implementation; null indicates that
148      *        {@link DefaultRowFactory the default row factory} should be used.
149      * @throws ClassNotFoundException if the the named row factory class cannot be located
150      * @throws IllegalAccessException if the row factory class or its nullary constructor is not accessible.
151      * @throws InstantiationException if the row factory represents an abstract class, an interface, an array class, a primitive
152      *         type, or void; or if the class has no nullary constructor; or if the instantiation fails for some other reason.
153      * @throws ClassCastException if the instantiated row factory does not implement the {@link RowFactory} interface
154      */
155     public synchronized void setRowFactoryClassName( String rowFactoryClassName )
156         throws ClassNotFoundException, IllegalAccessException, InstantiationException {
157         // Make sure that it's going to work
158         createRowFactory(rowFactoryClassName);
159         this.rowFactoryClassName = rowFactoryClassName;
160     }
161 
162     /**
163      * Parse the given row into its constituent columns.
164      * 
165      * @param row the row to be parsed
166      * @return an array of columns; never null
167      */
168     protected abstract String[] parseLine( String row );
169 
170     /**
171      * Creates an instance of the {@link #getRowFactoryClassName() row factory} configured for this sequencer.
172      * 
173      * @param className the name of the class to configure; null indicates that the {@link DefaultRowFactory default row factory}
174      *        should be used.
175      * @return an implementation of the named class; never null
176      * @throws ClassNotFoundException if the the named row factory class cannot be located
177      * @throws IllegalAccessException if the row factory class or its nullary constructor is not accessible.
178      * @throws InstantiationException if the row factory represents an abstract class, an interface, an array class, a primitive
179      *         type, or void; or if the class has no nullary constructor; or if the instantiation fails for some other reason.
180      */
181     protected synchronized RowFactory createRowFactory( String className )
182         throws ClassNotFoundException, IllegalAccessException, InstantiationException {
183         if (this.rowFactoryClassName == null) {
184             return new DefaultRowFactory();
185         }
186 
187         Class<?> rowFactoryClass = Class.forName(this.rowFactoryClassName);
188         RowFactory rowFactory = (RowFactory)rowFactoryClass.newInstance();
189 
190         return rowFactory;
191     }
192 }