/*
* Copyright 2016 DiffPlug
*
* 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.diffplug.spotless.generic;
import java.io.Serializable;
import java.util.Objects;
import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
/** Simple step which checks for consistent indentation characters. */
public final class IndentStep {
// prevent direct instantiation
private IndentStep() {}
public enum Type {
TAB, SPACE;
private <T> T tabSpace(T tab, T space) {
return this == TAB ? tab : space;
}
/** Synonym for {@link IndentStep#create(Type, int)}. */
public FormatterStep create(int numSpacesPerTab) {
return IndentStep.create(this, numSpacesPerTab);
}
}
/** Creates a step which will indent with the given type of whitespace, converting between tabs and spaces at the given ratio. */
public static FormatterStep create(Type type, int numSpacesPerTab) {
Objects.requireNonNull(type, "type");
return FormatterStep.create("indentWith" + type.tabSpace("Tabs", "Spaces"),
new State(type, numSpacesPerTab), State::toFormatter);
}
private static class State implements Serializable {
private static final long serialVersionUID = 1L;
final Type type;
final int numSpacesPerTab;
State(Type type, int numSpacesPerTab) {
this.type = type;
this.numSpacesPerTab = numSpacesPerTab;
}
FormatterFunc toFormatter() {
return new Runtime(this)::format;
}
}
static class Runtime {
final State state;
final StringBuilder builder = new StringBuilder();
Runtime(State state) {
this.state = state;
}
String format(String raw) {
// reset the buffer
builder.setLength(0);
int lineStart = 0; // beginning of line
do {
int contentStart = lineStart; // beginning of non-whitespace
int numSpaces = 0;
char c;
while (contentStart < raw.length() && isSpaceOrTab(c = raw.charAt(contentStart))) {
switch (c) {
case ' ':
++numSpaces;
break;
case '\t':
numSpaces += state.numSpacesPerTab;
break;
default:
throw new IllegalArgumentException("Unexpected char " + c);
}
++contentStart;
}
// add the leading space in a canonical way
if (numSpaces > 0) {
switch (state.type) {
case SPACE:
for (int i = 0; i < numSpaces; ++i) {
builder.append(' ');
}
break;
case TAB:
for (int i = 0; i < numSpaces / state.numSpacesPerTab; ++i) {
builder.append('\t');
}
break;
default:
throw new IllegalArgumentException("Unexpected enum " + state.type);
}
}
// find the start of the next line
lineStart = raw.indexOf('\n', contentStart);
if (lineStart == -1) {
// if we're at the end, append all of it
builder.append(raw.subSequence(contentStart, raw.length()));
return builder.toString();
} else {
// increment lineStart by 1 so that we start after the newline next time
++lineStart;
builder.append(raw.subSequence(contentStart, lineStart));
}
} while (true);
}
}
private static boolean isSpaceOrTab(char c) {
return c == ' ' || c == '\t';
}
}