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 }