View Javadoc
1   package org.apache.maven.plugins.enforcer.utils;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.FilterReader;
23  import java.io.IOException;
24  import java.io.Reader;
25  
26  /**
27   * Converts Unix line separators to Windows ones and vice-versa.
28   */
29  public class NormalizeLineSeparatorReader
30      extends FilterReader
31  {
32  
33      private static final int EOL = -1;
34  
35      /**
36       * Type representing either Unix or Windows line separators
37       */
38      public enum LineSeparator
39      {
40          WINDOWS( "\r\n", null ), UNIX( "\n", '\r' );
41  
42          private final char[] separatorChars;
43  
44          private final Character notPrecededByChar;
45  
46          LineSeparator( String separator, Character notPrecededByChar )
47          {
48              separatorChars = separator.toCharArray();
49              this.notPrecededByChar = notPrecededByChar;
50          }
51  
52          enum MatchResult
53          {
54              NO_MATCH,
55              POTENTIAL_MATCH,
56              MATCH;
57          }
58  
59          /**
60           * Checks if two given characters match the line separator represented by this object.
61           * @param currentCharacter the character to check against
62           * @param previousCharacter optional previous character (may be {@code null})
63           * @return one of {@link MatchResult}
64           */
65          public MatchResult matches( char currentCharacter, Character previousCharacter )
66          {
67              int len = separatorChars.length;
68              if ( currentCharacter == separatorChars[len - 1] )
69              {
70                  if ( len > 1 )
71                  {
72                      if ( previousCharacter == null || previousCharacter != separatorChars[len - 1] )
73                      {
74                          return MatchResult.NO_MATCH;
75                      }
76                  }
77                  if ( notPrecededByChar != null )
78                  {
79                      if ( previousCharacter != null && notPrecededByChar == previousCharacter )
80                      {
81                          return MatchResult.NO_MATCH;
82                      }
83                  }
84                  return MatchResult.MATCH;
85              }
86              else if ( len > 1 && currentCharacter == separatorChars[len - 2] )
87              {
88                  return MatchResult.POTENTIAL_MATCH;
89              }
90              return MatchResult.NO_MATCH;
91          }
92      };
93  
94      final LineSeparator lineSeparator;
95  
96      Character bufferedCharacter;
97  
98      Character previousCharacter;
99  
100     public NormalizeLineSeparatorReader( Reader reader, LineSeparator lineSeparator )
101     {
102         super( reader );
103         this.lineSeparator = lineSeparator;
104         bufferedCharacter = null;
105     }
106 
107     @Override
108     public int read( char[] cbuf, int off, int len )
109         throws IOException
110     {
111         int n;
112         for ( n = off; n < off + len; n++ )
113         {
114             int readResult = read();
115             if ( readResult == EOL )
116             {
117                 return n == 0 ? EOL : n;
118             }
119             else
120             {
121                 cbuf[n] = (char) readResult;
122             }
123         }
124         return n;
125     }
126 
127     @Override
128     public int read()
129         throws IOException
130     {
131         // spool buffered characters, if any
132         if ( bufferedCharacter != null )
133         {
134             char localBuffer = bufferedCharacter;
135             bufferedCharacter = null;
136             return localBuffer;
137         }
138         int readResult = super.read();
139         if ( readResult == EOL )
140         {
141             return readResult;
142         }
143         char currentCharacter = (char) readResult;
144         if ( lineSeparator == LineSeparator.UNIX )
145         {
146             switch ( LineSeparator.WINDOWS.matches( currentCharacter, previousCharacter ) )
147             {
148                 case MATCH:
149                     return lineSeparator.separatorChars[0];
150                 case POTENTIAL_MATCH:
151                     previousCharacter = currentCharacter;
152                     return read();
153                 default:
154                     // fall-through
155             }
156         }
157         else
158         { // WINDOWS
159             // if current is unix, convert
160             switch ( LineSeparator.UNIX.matches( currentCharacter, previousCharacter ) )
161             {
162                 case MATCH:
163                     bufferedCharacter = lineSeparator.separatorChars[1];
164                     // set buffered character and return current
165                     return lineSeparator.separatorChars[0];
166                 case POTENTIAL_MATCH:
167                     // invalid option
168                     throw new IllegalStateException( "No potential matches expected for Unix line separator" );
169                 default:
170                     // fall-through
171             }
172         }
173         previousCharacter = currentCharacter;
174         return currentCharacter;
175     }
176 
177 }