/** * Copyright 2011-2017 Asakusa Framework Team. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.asakusafw.dmdl.directio.tsv.driver; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.asakusafw.dmdl.directio.tsv.driver.TsvFieldTrait.Kind; import com.asakusafw.dmdl.directio.tsv.driver.TsvFormatTrait.Configuration; import com.asakusafw.dmdl.directio.util.CodecNames; import com.asakusafw.dmdl.directio.util.DirectFileInputDescriptionGenerator; import com.asakusafw.dmdl.directio.util.DirectFileOutputDescriptionGenerator; import com.asakusafw.dmdl.java.emitter.EmitContext; import com.asakusafw.dmdl.java.spi.JavaDataModelDriver; import com.asakusafw.dmdl.semantics.ModelDeclaration; import com.asakusafw.dmdl.semantics.PropertyDeclaration; import com.asakusafw.dmdl.semantics.Type; import com.asakusafw.dmdl.semantics.type.BasicType; import com.asakusafw.runtime.directio.BinaryStreamFormat; import com.asakusafw.runtime.directio.hadoop.ConfigurableBinaryStreamFormat; import com.asakusafw.runtime.directio.util.DelimiterRangeInputStream; import com.asakusafw.runtime.io.ModelInput; import com.asakusafw.runtime.io.ModelOutput; import com.asakusafw.runtime.io.TsvEmitter; import com.asakusafw.runtime.io.TsvParser; import com.asakusafw.runtime.value.StringOption; import com.asakusafw.utils.java.model.syntax.ClassDeclaration; import com.asakusafw.utils.java.model.syntax.Expression; import com.asakusafw.utils.java.model.syntax.ExpressionStatement; import com.asakusafw.utils.java.model.syntax.FieldDeclaration; import com.asakusafw.utils.java.model.syntax.FormalParameterDeclaration; import com.asakusafw.utils.java.model.syntax.InfixOperator; import com.asakusafw.utils.java.model.syntax.MethodDeclaration; import com.asakusafw.utils.java.model.syntax.ModelFactory; import com.asakusafw.utils.java.model.syntax.Name; import com.asakusafw.utils.java.model.syntax.SimpleName; import com.asakusafw.utils.java.model.syntax.Statement; import com.asakusafw.utils.java.model.syntax.TypeBodyDeclaration; import com.asakusafw.utils.java.model.syntax.WildcardBoundKind; import com.asakusafw.utils.java.model.util.AttributeBuilder; import com.asakusafw.utils.java.model.util.ExpressionBuilder; import com.asakusafw.utils.java.model.util.JavadocBuilder; import com.asakusafw.utils.java.model.util.Models; import com.asakusafw.utils.java.model.util.TypeBuilder; /** * Emits {@link BinaryStreamFormat} implementations. * @since 0.2.5 * @version 0.5.3 */ public class TsvFormatEmitter extends JavaDataModelDriver { static final Logger LOG = LoggerFactory.getLogger(TsvFormatEmitter.class); /** * Category name for TSV format. */ public static final String CATEGORY_STREAM = "tsv"; //$NON-NLS-1$ @Override public void generateResources(EmitContext context, ModelDeclaration model) throws IOException { if (isTarget(model) == false) { return; } checkPropertyType(model); Name supportName = generateFormat(context, model); generateInputDescription(context, supportName, model); generateOutputDescription(context, supportName, model); } private Name generateFormat(EmitContext context, ModelDeclaration model) throws IOException { assert context != null; assert model != null; EmitContext next = new EmitContext( context.getSemantics(), context.getConfiguration(), model, CATEGORY_STREAM, "{0}TsvFormat"); //$NON-NLS-1$ LOG.debug("Generating TSV format for {}", //$NON-NLS-1$ context.getQualifiedTypeName().toNameString()); FormatGenerator.emit(next, model, model.getTrait(TsvFormatTrait.class).getConfiguration()); LOG.debug("Generated TSV format for {}: {}", //$NON-NLS-1$ context.getQualifiedTypeName().toNameString(), next.getQualifiedTypeName().toNameString()); return next.getQualifiedTypeName(); } private void generateInputDescription( EmitContext context, Name formatClassName, ModelDeclaration model) throws IOException { EmitContext next = new EmitContext( context.getSemantics(), context.getConfiguration(), model, CATEGORY_STREAM, "Abstract{0}TsvInputDescription"); //$NON-NLS-1$ DirectFileInputDescriptionGenerator.Description desc = new DirectFileInputDescriptionGenerator.Description( "TSV file input", context.getQualifiedTypeName()); //$NON-NLS-1$ desc.setFormatClassName(formatClassName); DirectFileInputDescriptionGenerator.generate(next, desc); } private void generateOutputDescription( EmitContext context, Name formatClassName, ModelDeclaration model) throws IOException { EmitContext next = new EmitContext( context.getSemantics(), context.getConfiguration(), model, CATEGORY_STREAM, "Abstract{0}TsvOutputDescription"); //$NON-NLS-1$ DirectFileOutputDescriptionGenerator.Description desc = new DirectFileOutputDescriptionGenerator.Description( "TSV file output", context.getQualifiedTypeName()); //$NON-NLS-1$ desc.setFormatClassName(formatClassName); DirectFileOutputDescriptionGenerator.generate(next, desc); } private boolean isTarget(ModelDeclaration model) { assert model != null; TsvFormatTrait trait = model.getTrait(TsvFormatTrait.class); return trait != null; } private void checkPropertyType(ModelDeclaration model) throws IOException { assert model != null; for (PropertyDeclaration prop : model.getDeclaredProperties()) { if (isValueField(prop)) { Type type = prop.getType(); if ((type instanceof BasicType) == false) { throw new IOException(MessageFormat.format( Messages.getString("TsvFormatEmitter.errorUnsupportedType"), //$NON-NLS-1$ type, prop.getOwner().getName().identifier, prop.getName().identifier)); } } } } static boolean isValueField(PropertyDeclaration property) { assert property != null; return TsvFieldTrait.getKind(property, Kind.VALUE) == Kind.VALUE; } private static final class FormatGenerator { private static final String NAME_ADD_HEADER = "addHeader"; //$NON-NLS-1$ private static final String NAME_SKIP_HEADER = "skipHeader"; //$NON-NLS-1$ private static final String NAME_READER = "RecordReader"; //$NON-NLS-1$ private static final String NAME_WRITER = "RecordWriter"; //$NON-NLS-1$ private final EmitContext context; private final ModelDeclaration model; private final Configuration conf; private final ModelFactory f; private FormatGenerator(EmitContext context, ModelDeclaration model, Configuration configuration) { assert context != null; assert model != null; assert configuration != null; this.context = context; this.model = model; this.conf = configuration; this.f = context.getModelFactory(); } static void emit(EmitContext context, ModelDeclaration model, Configuration conf) throws IOException { assert context != null; assert model != null; assert conf != null; FormatGenerator emitter = new FormatGenerator(context, model, conf); emitter.emit(); } private void emit() throws IOException { Class<?> superClass = isHadoopConfRequired() ? ConfigurableBinaryStreamFormat.class : BinaryStreamFormat.class; ClassDeclaration decl = f.newClassDeclaration( new JavadocBuilder(f) .inline("TSV format for {0}.", d -> d.linkType(context.resolve(model.getSymbol()))) .toJavadoc(), new AttributeBuilder(f) .Public() .toAttributes(), context.getTypeName(), f.newParameterizedType( context.resolve(superClass), context.resolve(model.getSymbol())), Collections.emptyList(), createMembers()); context.emit(decl); } private List<TypeBodyDeclaration> createMembers() { List<TypeBodyDeclaration> results = new ArrayList<>(); results.add(createGetSupportedType()); results.add(createGetPreferredFragmentSize()); results.add(createGetMinimumFragmentSize()); results.add(createCreateReader()); results.add(createCreateWriter()); if (conf.isEnableHeader()) { results.add(createAddHeader()); results.add(createSkipHeader()); } results.add(createReaderClass()); results.add(createWriterClass()); return results; } private MethodDeclaration createGetSupportedType() { MethodDeclaration decl = f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Override.class)) .Public() .toAttributes(), f.newParameterizedType( context.resolve(Class.class), context.resolve(model.getSymbol())), f.newSimpleName("getSupportedType"), //$NON-NLS-1$ Collections.emptyList(), Arrays.asList(new Statement[] { new TypeBuilder(f, context.resolve(model.getSymbol())) .dotClass() .toReturnStatement() })); return decl; } private MethodDeclaration createGetPreferredFragmentSize() { Expression value = Models.toLiteral(f, -1L); return f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Override.class)) .Public() .toAttributes(), context.resolve(long.class), f.newSimpleName("getPreferredFragmentSize"), //$NON-NLS-1$ Collections.emptyList(), Collections.singletonList(new ExpressionBuilder(f, value).toReturnStatement())); } private MethodDeclaration createGetMinimumFragmentSize() { boolean fastMode = isFastMode(); Expression value = fastMode ? new TypeBuilder(f, context.resolve(Long.class)).field("MAX_VALUE").toExpression() //$NON-NLS-1$ : Models.toLiteral(f, -1L); return f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Override.class)) .Public() .toAttributes(), context.resolve(long.class), f.newSimpleName("getMinimumFragmentSize"), //$NON-NLS-1$ Collections.emptyList(), Collections.singletonList(new ExpressionBuilder(f, value).toReturnStatement())); } private boolean isFastMode() { return conf.isAllowLinefeed() == false && conf.getCodecName() == null; } private boolean isHadoopConfRequired() { return conf.getCodecName() != null; } private MethodDeclaration createCreateReader() { SimpleName dataType = f.newSimpleName("dataType"); //$NON-NLS-1$ SimpleName path = f.newSimpleName("path"); //$NON-NLS-1$ SimpleName stream = f.newSimpleName("stream"); //$NON-NLS-1$ SimpleName offset = f.newSimpleName("offset"); //$NON-NLS-1$ SimpleName fragmentSize = f.newSimpleName("fragmentSize"); //$NON-NLS-1$ List<Statement> statements = new ArrayList<>(); statements.add(createNullCheck(dataType)); statements.add(createNullCheck(path)); statements.add(createNullCheck(stream)); Expression isNotHead = new ExpressionBuilder(f, offset) .apply(InfixOperator.GREATER, Models.toLiteral(f, 0L)) .toExpression(); if (isFastMode() == false) { statements.add(f.newIfStatement( isNotHead, f.newBlock(new TypeBuilder(f, context.resolve(IllegalArgumentException.class)) .newObject(Models.toLiteral(f, MessageFormat.format( "{0} does not support fragmentation.", //$NON-NLS-1$ context.getQualifiedTypeName().toNameString()))) .toThrowStatement()))); } SimpleName fragmentInput = f.newSimpleName("fragmentInput"); //$NON-NLS-1$ if (isFastMode()) { statements.add(new TypeBuilder(f, context.resolve(DelimiterRangeInputStream.class)) .newObject( blessInputStream(stream), Models.toLiteral(f, '\n'), f.newConditionalExpression( f.newInfixExpression( fragmentSize, InfixOperator.GREATER_EQUALS, Models.toLiteral(f, 0L)), fragmentSize, new TypeBuilder(f, context.resolve(Long.class)) .field("MAX_VALUE") //$NON-NLS-1$ .toExpression()), isNotHead) .toLocalVariableDeclaration(context.resolve(InputStream.class), fragmentInput)); } else { statements.add(new ExpressionBuilder(f, blessInputStream(stream)) .toLocalVariableDeclaration(context.resolve(InputStream.class), fragmentInput)); } if (conf.isEnableHeader()) { statements.add(f.newIfStatement( new ExpressionBuilder(f, offset) .apply(InfixOperator.EQUALS, Models.toLiteral(f, 0L)) .toExpression(), f.newBlock(new ExpressionBuilder(f, f.newThis()) .method(NAME_SKIP_HEADER, fragmentInput) .toStatement()))); } SimpleName parser = f.newSimpleName("parser"); //$NON-NLS-1$ statements.add(new TypeBuilder(f, context.resolve(TsvParser.class)) .newObject(new TypeBuilder(f, context.resolve(InputStreamReader.class)) .newObject(fragmentInput, Models.toLiteral(f, conf.getCharsetName())) .toExpression()) .toLocalVariableDeclaration(context.resolve(TsvParser.class), parser)); List<Expression> arguments = new ArrayList<>(); arguments.add(parser); if (hasFileName()) { arguments.add(new TypeBuilder(f, context.resolve(StringOption.class)) .newObject(path) .toExpression()); } statements.add(new TypeBuilder(f, f.newNamedType(f.newSimpleName(NAME_READER))) .newObject(arguments) .toReturnStatement()); MethodDeclaration decl = f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Override.class)) .Public() .toAttributes(), Collections.emptyList(), f.newParameterizedType( context.resolve(ModelInput.class), context.resolve(model.getSymbol())), f.newSimpleName("createInput"), //$NON-NLS-1$ Arrays.asList( f.newFormalParameterDeclaration( f.newParameterizedType( context.resolve(Class.class), f.newWildcard( WildcardBoundKind.UPPER_BOUNDED, context.resolve(model.getSymbol()))), dataType), f.newFormalParameterDeclaration(context.resolve(String.class), path), f.newFormalParameterDeclaration(context.resolve(InputStream.class), stream), f.newFormalParameterDeclaration(context.resolve(long.class), offset), f.newFormalParameterDeclaration(context.resolve(long.class), fragmentSize)), 0, Arrays.asList(context.resolve(IOException.class)), f.newBlock(statements)); return decl; } private MethodDeclaration createCreateWriter() { SimpleName dataType = f.newSimpleName("dataType"); //$NON-NLS-1$ SimpleName path = f.newSimpleName("path"); //$NON-NLS-1$ SimpleName stream = f.newSimpleName("stream"); //$NON-NLS-1$ List<Statement> statements = new ArrayList<>(); statements.add(createNullCheck(path)); statements.add(createNullCheck(stream)); SimpleName emitter = f.newSimpleName("emitter"); //$NON-NLS-1$ statements.add(new TypeBuilder(f, context.resolve(TsvEmitter.class)) .newObject(new TypeBuilder(f, context.resolve(OutputStreamWriter.class)) .newObject(blessOutputStream(stream), Models.toLiteral(f, conf.getCharsetName())) .toExpression()) .toLocalVariableDeclaration(context.resolve(TsvEmitter.class), emitter)); if (conf.isEnableHeader()) { statements.add(new ExpressionBuilder(f, f.newThis()) .method(NAME_ADD_HEADER, emitter) .toStatement()); } statements.add(new TypeBuilder(f, f.newNamedType(f.newSimpleName(NAME_WRITER))) .newObject(emitter) .toReturnStatement()); MethodDeclaration decl = f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Override.class)) .Public() .toAttributes(), Collections.emptyList(), context.resolve(f.newParameterizedType( context.resolve(ModelOutput.class), context.resolve(model.getSymbol()))), f.newSimpleName("createOutput"), //$NON-NLS-1$ Arrays.asList( f.newFormalParameterDeclaration( f.newParameterizedType( context.resolve(Class.class), f.newWildcard( WildcardBoundKind.UPPER_BOUNDED, context.resolve(model.getSymbol()))), dataType), f.newFormalParameterDeclaration(context.resolve(String.class), path), f.newFormalParameterDeclaration(context.resolve(OutputStream.class), stream)), 0, Arrays.asList(context.resolve(IOException.class)), f.newBlock(statements)); return decl; } private MethodDeclaration createAddHeader() { SimpleName emitter = f.newSimpleName("emitter"); //$NON-NLS-1$ SimpleName buf = f.newSimpleName("buf"); //$NON-NLS-1$ List<Statement> statements = new ArrayList<>(); statements.add(new TypeBuilder(f, context.resolve(StringOption.class)) .newObject() .toLocalVariableDeclaration(context.resolve(StringOption.class), buf)); for (PropertyDeclaration property : model.getDeclaredProperties()) { switch (TsvFieldTrait.getKind(property, Kind.VALUE)) { case VALUE: statements.add(new ExpressionBuilder(f, buf) .method("modify", Models.toLiteral(f, TsvFieldTrait.getFieldName(property))) //$NON-NLS-1$ .toStatement()); statements.add(new ExpressionBuilder(f, emitter) .method("emit", buf) //$NON-NLS-1$ .toStatement()); break; default: // ignored break; } } statements.add(new ExpressionBuilder(f, emitter) .method("endRecord") //$NON-NLS-1$ .toStatement()); MethodDeclaration decl = f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Deprecated.class)) .Private() .toAttributes(), Collections.emptyList(), context.resolve(void.class), f.newSimpleName(NAME_ADD_HEADER), Arrays.asList(f.newFormalParameterDeclaration(context.resolve(TsvEmitter.class), emitter)), 0, Arrays.asList(context.resolve(IOException.class)), f.newBlock(statements)); return decl; } private MethodDeclaration createSkipHeader() { SimpleName input = f.newSimpleName("stream"); //$NON-NLS-1$ List<Statement> body = new ArrayList<>(); SimpleName c = f.newSimpleName("c"); //$NON-NLS-1$ body.add(new ExpressionBuilder(f, input) .method("read") //$NON-NLS-1$ .toLocalVariableDeclaration(context.resolve(int.class), c)); body.add(f.newIfStatement( new ExpressionBuilder(f, c) .apply(InfixOperator.LESS, Models.toLiteral(f, 0)) .toExpression(), f.newBlock(f.newBreakStatement()))); body.add(f.newIfStatement( new ExpressionBuilder(f, c) .apply(InfixOperator.EQUALS, Models.toLiteral(f, '\n')) .toExpression(), f.newBlock(f.newBreakStatement()))); MethodDeclaration decl = f.newMethodDeclaration( null, new AttributeBuilder(f) .Private() .toAttributes(), Collections.emptyList(), context.resolve(void.class), f.newSimpleName(NAME_SKIP_HEADER), Arrays.asList(f.newFormalParameterDeclaration(context.resolve(InputStream.class), input)), 0, Arrays.asList(context.resolve(IOException.class)), f.newBlock(f.newWhileStatement(Models.toLiteral(f, true), f.newBlock(body)))); return decl; } private Expression blessInputStream(SimpleName stream) { Expression codec = createCompressionCodec(); if (codec == null) { return stream; } return new ExpressionBuilder(f, codec) .method("createInputStream", stream) //$NON-NLS-1$ .toExpression(); } private Expression blessOutputStream(SimpleName stream) { Expression codec = createCompressionCodec(); if (codec == null) { return stream; } return new ExpressionBuilder(f, codec) .method("createOutputStream", stream) //$NON-NLS-1$ .toExpression(); } private Expression createCompressionCodec() { String codecName = CodecNames.resolveCodecName(conf.getCodecName()); if (codecName == null) { return null; } assert isHadoopConfRequired(); return new TypeBuilder(f, context.resolve(Models.toName(f, "org.apache.hadoop.util.ReflectionUtils"))) //$NON-NLS-1$ .method("newInstance", //$NON-NLS-1$ new TypeBuilder(f, context.resolve(Models.toName(f, codecName))) .dotClass() .toExpression(), new ExpressionBuilder(f, f.newThis()) .method("getConf") //$NON-NLS-1$ .toExpression()) .toExpression(); } private Statement createNullCheck(SimpleName parameter) { assert parameter != null; return f.newIfStatement( new ExpressionBuilder(f, parameter) .apply(InfixOperator.EQUALS, Models.toNullLiteral(f)) .toExpression(), f.newBlock(new TypeBuilder(f, context.resolve(IllegalArgumentException.class)) .newObject(Models.toLiteral(f, MessageFormat.format( "{0} must not be null", //$NON-NLS-1$ parameter.getToken()))) .toThrowStatement())); } private ClassDeclaration createReaderClass() { SimpleName parser = f.newSimpleName("parser"); //$NON-NLS-1$ SimpleName path = f.newSimpleName("path"); //$NON-NLS-1$ List<TypeBodyDeclaration> members = new ArrayList<>(); List<ExpressionStatement> constructorStatements = new ArrayList<>(); List<FormalParameterDeclaration> constructorParameters = new ArrayList<>(); members.add(createPrivateField(TsvParser.class, parser)); constructorParameters.add(f.newFormalParameterDeclaration(context.resolve(TsvParser.class), parser)); constructorStatements.add(mapField(parser)); if (hasFileName()) { members.add(createPrivateField(StringOption.class, path)); constructorParameters.add(f.newFormalParameterDeclaration(context.resolve(StringOption.class), path)); constructorStatements.add(mapField(path)); } members.add(f.newConstructorDeclaration( null, new AttributeBuilder(f).toAttributes(), f.newSimpleName(NAME_READER), constructorParameters, constructorStatements)); SimpleName object = f.newSimpleName("object"); //$NON-NLS-1$ List<Statement> statements = new ArrayList<>(); statements.add(f.newIfStatement( new ExpressionBuilder(f, parser) .method("next") //$NON-NLS-1$ .apply(InfixOperator.EQUALS, Models.toLiteral(f, false)) .toExpression(), f.newBlock(new ExpressionBuilder(f, Models.toLiteral(f, false)) .toReturnStatement()))); for (PropertyDeclaration property : model.getDeclaredProperties()) { switch (TsvFieldTrait.getKind(property, Kind.VALUE)) { case VALUE: statements.add(new ExpressionBuilder(f, parser) .method("fill", new ExpressionBuilder(f, object) //$NON-NLS-1$ .method(context.getOptionGetterName(property)) .toExpression()) .toStatement()); break; case FILE_NAME: statements.add(new ExpressionBuilder(f, object) .method(context.getOptionSetterName(property), path) .toStatement()); break; default: // ignored break; } } statements.add(new ExpressionBuilder(f, parser) .method("endRecord") //$NON-NLS-1$ .toStatement()); statements.add(new ExpressionBuilder(f, Models.toLiteral(f, true)) .toReturnStatement()); members.add(f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Override.class)) .Public() .toAttributes(), Collections.emptyList(), context.resolve(boolean.class), f.newSimpleName("readTo"), //$NON-NLS-1$ Arrays.asList(f.newFormalParameterDeclaration(context.resolve(model.getSymbol()), object)), 0, Arrays.asList(context.resolve(IOException.class)), f.newBlock(statements))); members.add(f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Override.class)) .Public() .toAttributes(), Collections.emptyList(), context.resolve(void.class), f.newSimpleName("close"), //$NON-NLS-1$ Collections.emptyList(), 0, Arrays.asList(context.resolve(IOException.class)), f.newBlock(new ExpressionBuilder(f, parser) .method("close") //$NON-NLS-1$ .toStatement()))); return f.newClassDeclaration( null, new AttributeBuilder(f) .Private() .Static() .Final() .toAttributes(), f.newSimpleName(NAME_READER), null, Arrays.asList(f.newParameterizedType( context.resolve(ModelInput.class), context.resolve(model.getSymbol()))), members); } private ClassDeclaration createWriterClass() { SimpleName emitter = f.newSimpleName("emitter"); //$NON-NLS-1$ List<TypeBodyDeclaration> members = new ArrayList<>(); members.add(createPrivateField(TsvEmitter.class, emitter)); members.add(f.newConstructorDeclaration( null, new AttributeBuilder(f).toAttributes(), f.newSimpleName(NAME_WRITER), Arrays.asList(f.newFormalParameterDeclaration(context.resolve(TsvEmitter.class), emitter)), Arrays.asList(mapField(emitter)))); SimpleName object = f.newSimpleName("object"); //$NON-NLS-1$ List<Statement> statements = new ArrayList<>(); for (PropertyDeclaration property : model.getDeclaredProperties()) { if (isValueField(property)) { statements.add(new ExpressionBuilder(f, emitter) .method("emit", new ExpressionBuilder(f, object) //$NON-NLS-1$ .method(context.getOptionGetterName(property)) .toExpression()) .toStatement()); } } statements.add(new ExpressionBuilder(f, emitter) .method("endRecord") //$NON-NLS-1$ .toStatement()); members.add(f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Override.class)) .Public() .toAttributes(), Collections.emptyList(), context.resolve(void.class), f.newSimpleName("write"), //$NON-NLS-1$ Arrays.asList(f.newFormalParameterDeclaration(context.resolve(model.getSymbol()), object)), 0, Arrays.asList(context.resolve(IOException.class)), f.newBlock(statements))); members.add(f.newMethodDeclaration( null, new AttributeBuilder(f) .annotation(context.resolve(Override.class)) .Public() .toAttributes(), Collections.emptyList(), context.resolve(void.class), f.newSimpleName("close"), //$NON-NLS-1$ Collections.emptyList(), 0, Arrays.asList(context.resolve(IOException.class)), f.newBlock(new ExpressionBuilder(f, emitter) .method("close") //$NON-NLS-1$ .toStatement()))); return f.newClassDeclaration( null, new AttributeBuilder(f) .Private() .Static() .Final() .toAttributes(), f.newSimpleName(NAME_WRITER), null, Arrays.asList(f.newParameterizedType( context.resolve(ModelOutput.class), context.resolve(model.getSymbol()))), members); } private boolean hasFileName() { for (PropertyDeclaration property : model.getDeclaredProperties()) { if (TsvFieldTrait.getKind(property, Kind.VALUE) == Kind.FILE_NAME) { return true; } } return false; } private ExpressionStatement mapField(SimpleName name) { return new ExpressionBuilder(f, f.newThis()) .field(name) .assignFrom(name) .toStatement(); } private FieldDeclaration createPrivateField(Class<?> type, SimpleName name) { return f.newFieldDeclaration( null, new AttributeBuilder(f) .Private() .Final() .toAttributes(), context.resolve(type), name, null); } } }