#515, introduction of string definition handling in IndentationCorrectingWriter

This commit is contained in:
sjaakd 2015-05-11 20:57:15 +02:00
parent 2ece25a086
commit 124c2a4001
5 changed files with 292 additions and 28 deletions

View File

@ -29,6 +29,16 @@ import java.util.Arrays;
* This writer discards any leading whitespace characters following to a line break character. When the first
* non-whitespace character is written after a line break, the correct indentation characters are added, which is four
* whitespace characters per indentation level.
*
* <p>
* The state pattern is line oriented. It starts by writing text. Indentation is increased if a brace '('or
* brace '{' is encountered in the code to be generated and written out in state: IN_TEXT_START_OF_LINE. Whenever
* a line end occurs (PC or Linux style) the amount of enters is checked and at max set to 2.
*
* Whenever a string definition is encountered in the code that should be generated, increasing the indentation is
* stopped (so `{` and '(' are ignored) until the end of the string is encountered ('"'). To avoid writing a new
* indentation, the state then returns to IN_TEXT.
*
* <p>
* This is a very basic implementation which does not take into account comments, escaping etc.
*
@ -43,7 +53,7 @@ class IndentationCorrectingWriter extends Writer {
private static final String LINE_SEPARATOR = System.getProperty( "line.separator" );
private static final boolean IS_WINDOWS = System.getProperty( "os.name" ).startsWith( "Windows" );
private State currentState = State.IN_TEXT;
private State currentState = State.START_OF_LINE;
private final StateContext context;
IndentationCorrectingWriter(Writer out) {
@ -99,12 +109,22 @@ class IndentationCorrectingWriter extends Writer {
private enum State {
/**
* Within any text.
* Within any text, before encountering a String definition.
*/
IN_TEXT {
START_OF_LINE {
@Override
State doHandleCharacter(char c, StateContext context) {
switch ( c ) {
case '{':
case '(':
context.indentationLevel++;
return IN_TEXT;
case '}':
case ')':
context.indentationLevel--;
return IN_TEXT;
case '\"':
return IN_STRING;
case '\r':
return isWindows() ? IN_LINE_BREAK : AFTER_LINE_BREAK;
case '\n':
@ -142,27 +162,115 @@ class IndentationCorrectingWriter extends Writer {
flush( context );
}
private void flush(StateContext context) throws IOException {
if ( null != context.characters && context.currentIndex - context.lastStateChange > 0 ) {
context.writer.write(
context.characters,
context.lastStateChange,
context.currentIndex - context.lastStateChange
);
},
if ( DEBUG ) {
System.out.print(
new String(
java.util.Arrays.copyOfRange(
context.characters,
context.lastStateChange,
context.currentIndex
)
)
);
/**
* Within any text, but after a String (" ").
*/
IN_TEXT {
@Override
State doHandleCharacter(char c, StateContext context) {
switch ( c ) {
case '{':
case '(':
context.indentationLevel++;
return IN_TEXT;
case '}':
case ')':
context.indentationLevel--;
return IN_TEXT;
case '\"':
return IN_STRING;
case '\r':
return isWindows() ? IN_LINE_BREAK : AFTER_LINE_BREAK;
case '\n':
return AFTER_LINE_BREAK;
default:
return IN_TEXT;
}
}
/**
* Writes out the current text.
*/
@Override
void onExit(StateContext context) throws IOException {
flush( context );
}
/**
* Writes out the current text.
*/
@Override
void onBufferFinished(StateContext context) throws IOException {
flush( context );
}
},
/**
* In a String definition, Between un-escaped quotes " "
*/
IN_STRING {
@Override
State doHandleCharacter(char c, StateContext context) {
switch ( c ) {
case '\"':
return IN_TEXT;
case '\\':
return IN_STRING_ESCAPED_CHAR;
default:
return IN_STRING;
}
}
/**
* Writes out the current text.
*/
@Override
void onExit(StateContext context) throws IOException {
flush( context );
}
/**
* Writes out the current text.
*/
@Override
void onBufferFinished(StateContext context) throws IOException {
flush( context );
}
},
/**
* In a String, character following an escape character '\', should be ignored, can also be '"' that
* should be ignored.
*/
IN_STRING_ESCAPED_CHAR {
@Override
State doHandleCharacter(char c, StateContext context) {
// ignore escaped character
return IN_STRING;
}
/**
* Writes out the current text.
*/
@Override
void onExit(StateContext context) throws IOException {
flush( context );
}
/**
* Writes out the current text.
*/
@Override
void onBufferFinished(StateContext context) throws IOException {
flush( context );
}
},
/**
@ -186,7 +294,16 @@ class IndentationCorrectingWriter extends Writer {
AFTER_LINE_BREAK {
@Override
State doHandleCharacter(char c, StateContext context) {
switch ( c ) {
case '{':
case '(':
context.indentationLevel++;
return START_OF_LINE;
case '}':
case ')':
context.indentationLevel--;
return START_OF_LINE;
case '\r':
return isWindows() ? IN_LINE_BREAK : AFTER_LINE_BREAK;
case ' ':
@ -195,7 +312,7 @@ class IndentationCorrectingWriter extends Writer {
context.consecutiveLineBreaks++;
return AFTER_LINE_BREAK;
default:
return IN_TEXT;
return START_OF_LINE;
}
}
@ -220,12 +337,7 @@ class IndentationCorrectingWriter extends Writer {
};
final State handleCharacter(char c, StateContext context) throws IOException {
if ( c == '{' || c == '(' ) {
context.indentationLevel++;
}
else if ( c == '}' || c == ')' ) {
context.indentationLevel--;
}
return doHandleCharacter( c, context );
}
@ -245,6 +357,28 @@ class IndentationCorrectingWriter extends Writer {
void onBufferFinished(StateContext context) throws IOException {
}
protected void flush(StateContext context) throws IOException {
if ( null != context.characters && context.currentIndex - context.lastStateChange > 0 ) {
context.writer.write(
context.characters,
context.lastStateChange,
context.currentIndex - context.lastStateChange
);
if ( DEBUG ) {
System.out.print(
new String(
java.util.Arrays.copyOfRange(
context.characters,
context.lastStateChange,
context.currentIndex
)
)
);
}
}
}
}
/**

View File

@ -0,0 +1,33 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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 org.mapstruct.ap.test.bugs._515;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public abstract class Issue515Mapper {
public static final Issue515Mapper INSTANCE = Mappers.getMapper( Issue515Mapper.class );
@Mapping( target = "id", expression = "java(\"blah)\\\"\")" )
public abstract Target map(Source source);
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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 org.mapstruct.ap.test.bugs._515;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
/**
* Reproducer for https://github.com/mapstruct/mapstruct/issues/515.
*
* @author Sjaak Derksen
*/
@IssueKey( "515" )
@RunWith(AnnotationProcessorTestRunner.class)
public class Issue515Test {
@Test
@WithClasses( { Issue515Mapper.class, Source.class, Target.class } )
public void shouldIgnoreParanthesesOpenInStringDefinition() {
}
}

View File

@ -0,0 +1,23 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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 org.mapstruct.ap.test.bugs._515;
public class Source {
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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 org.mapstruct.ap.test.bugs._515;
public class Target {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}