001package org.apache.maven.plugins.enforcer.utils;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.FilterReader;
023import java.io.IOException;
024import java.io.Reader;
025
026/**
027 * Converts Unix line separators to Windows ones and vice-versa.
028 */
029public class NormalizeLineSeparatorReader
030    extends FilterReader
031{
032
033    private static final int EOL = -1;
034
035    /**
036     * Type representing either Unix or Windows line separators
037     */
038    public enum LineSeparator
039    {
040        WINDOWS( "\r\n", null ), UNIX( "\n", '\r' );
041
042        private final char[] separatorChars;
043
044        private final Character notPrecededByChar;
045
046        LineSeparator( String separator, Character notPrecededByChar )
047        {
048            separatorChars = separator.toCharArray();
049            this.notPrecededByChar = notPrecededByChar;
050        }
051
052        enum MatchResult
053        {
054            NO_MATCH,
055            POTENTIAL_MATCH,
056            MATCH;
057        }
058
059        /**
060         * Checks if two given characters match the line separator represented by this object.
061         * @param currentCharacter the character to check against
062         * @param previousCharacter optional previous character (may be {@code null})
063         * @return one of {@link MatchResult}
064         */
065        public MatchResult matches( char currentCharacter, Character previousCharacter )
066        {
067            int len = separatorChars.length;
068            if ( currentCharacter == separatorChars[len - 1] )
069            {
070                if ( len > 1 )
071                {
072                    if ( previousCharacter == null || previousCharacter != separatorChars[len - 1] )
073                    {
074                        return MatchResult.NO_MATCH;
075                    }
076                }
077                if ( notPrecededByChar != null )
078                {
079                    if ( previousCharacter != null && notPrecededByChar == previousCharacter )
080                    {
081                        return MatchResult.NO_MATCH;
082                    }
083                }
084                return MatchResult.MATCH;
085            }
086            else if ( len > 1 && currentCharacter == separatorChars[len - 2] )
087            {
088                return MatchResult.POTENTIAL_MATCH;
089            }
090            return MatchResult.NO_MATCH;
091        }
092    };
093
094    final LineSeparator lineSeparator;
095
096    Character bufferedCharacter;
097
098    Character previousCharacter;
099
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}