diff --git a/processor/src/main/java/org/mapstruct/ap/writer/IndentationCorrectingWriter.java b/processor/src/main/java/org/mapstruct/ap/writer/IndentationCorrectingWriter.java index b1a36dfbb..789c6d52a 100644 --- a/processor/src/main/java/org/mapstruct/ap/writer/IndentationCorrectingWriter.java +++ b/processor/src/main/java/org/mapstruct/ap/writer/IndentationCorrectingWriter.java @@ -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. + * + *
+ * 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. + * *
* 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 + ) + ) + ); + } + } + } } /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Mapper.java new file mode 100644 index 000000000..6b18ae6cb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Mapper.java @@ -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); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Test.java new file mode 100644 index 000000000..66b411374 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Test.java @@ -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() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Source.java new file mode 100644 index 000000000..eab49fd99 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Source.java @@ -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 { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Target.java new file mode 100644 index 000000000..d20d6ade1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Target.java @@ -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; + } + +}