JacksonJsonFormatterWrapper.java
/*
* Copyright (c) 2020 bahlef.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
* Contributors:
* bahlef - initial API and implementation and/or initial documentation
*/
package de.funfried.netbeans.plugins.external.formatter.json.jackson;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.core.util.Separators;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import de.funfried.netbeans.plugins.external.formatter.exceptions.FormattingFailedException;
/**
* Wrapper class to the revelc.net formatter implementation.
*
* @author bahlef
*/
public final class JacksonJsonFormatterWrapper {
/**
* Package private Constructor for creating a new instance of {@link JacksonJsonFormatterWrapper}.
*/
JacksonJsonFormatterWrapper() {
}
/**
* Formats the given {@code code} with the given configurations and returns
* the formatted code.
*
* @param code the unformatted code
* @param lineFeed the line feed to use for formatting
* @param options the {@link Options}
*
* @return the formatted code
*/
@CheckForNull
public String format(String code, String lineFeed, Options options) throws FormattingFailedException {
if (code == null) {
return null;
}
if (lineFeed == null) {
lineFeed = System.getProperty("line.separator");
}
if (options == null) {
options = new Options();
}
int indentSize = options.getIndentSize();
int spacesPerTab = options.getSpacesPerTab();
boolean spaceBeforeSeparator = options.isSpaceBeforeSeparator();
boolean expandTabsToSpaces = options.isExpandTabsToSpaces();
String indentString;
if (expandTabsToSpaces) {
indentString = StringUtils.repeat(" ", indentSize);
} else {
indentString = StringUtils.repeat("\t", indentSize / spacesPerTab) + StringUtils.repeat(" ", indentSize % spacesPerTab);
}
DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(indentString, lineFeed);
DefaultPrettyPrinter printer = new JacksonPrettyPrinter(spaceBeforeSeparator);
printer.withSeparators(DefaultPrettyPrinter.DEFAULT_SEPARATORS);
printer.indentObjectsWith(indenter);
printer.indentArraysWith(indenter);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.setDefaultPrettyPrinter(printer);
JsonFactory factory = objectMapper.getFactory();
factory.enable(JsonParser.Feature.ALLOW_COMMENTS);
factory.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
factory.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
factory.enable(JsonParser.Feature.ALLOW_YAML_COMMENTS);
return format(objectMapper, code);
}
/**
* Formats the given {@code code} with the given {@link ObjectMapper} and returns
* the formatted code.
*
* @param objectMapper the {@link ObjectMapper}
* @param code the unformatted code
*
* @return the formatted code
*
* @throws FormattingFailedException if the given code could not be formatted
* with this formatter
*/
@CheckForNull
private String format(ObjectMapper objectMapper, @NonNull String code) throws FormattingFailedException {
String formattedCode;
try {
Object json = objectMapper.readValue(code, Object.class);
formattedCode = objectMapper.writer().writeValueAsString(json);
if (Objects.equals(code, formattedCode)) {
return null;
}
} catch (JsonProcessingException ex) {
throw new FormattingFailedException(ex);
}
return formattedCode;
}
/**
* Custom implementation of the {@link DefaultPrettyPrinter}.
*/
private static class JacksonPrettyPrinter extends DefaultPrettyPrinter {
private static final long serialVersionUID = 1L;
private final boolean spaceBeforeSeparator;
/**
* Creates a new instance of {@link JacksonPrettyPrinter}.
*
* @param spaceBeforeSeparator {@code true} to add a space between the
* key and before the value separator
*/
public JacksonPrettyPrinter(boolean spaceBeforeSeparator) {
super();
this.spaceBeforeSeparator = spaceBeforeSeparator;
}
/**
* {@inheritDoc}
*/
@Override
public DefaultPrettyPrinter createInstance() {
return new DefaultPrettyPrinter(this);
}
/**
* {@inheritDoc}
*/
@Override
public DefaultPrettyPrinter withSeparators(Separators separators) {
this._separators = separators;
this._objectFieldValueSeparatorWithSpaces = (this.spaceBeforeSeparator ? " " : "") + separators.getObjectFieldValueSeparator() + " ";
return this;
}
}
public static class Options {
private int indentSize = 2;
private int spacesPerTab = 2;
private boolean spaceBeforeSeparator = false;
private boolean expandTabsToSpaces = true;
public int getIndentSize() {
return indentSize;
}
public void setIndentSize(int indentSize) {
this.indentSize = indentSize;
}
public int getSpacesPerTab() {
return spacesPerTab;
}
public void setSpacesPerTab(int spacesPerTab) {
this.spacesPerTab = spacesPerTab;
}
public boolean isSpaceBeforeSeparator() {
return spaceBeforeSeparator;
}
public void setSpaceBeforeSeparator(boolean spaceBeforeSeparator) {
this.spaceBeforeSeparator = spaceBeforeSeparator;
}
public boolean isExpandTabsToSpaces() {
return expandTabsToSpaces;
}
public void setExpandTabsToSpaces(boolean expandTabsToSpaces) {
this.expandTabsToSpaces = expandTabsToSpaces;
}
}
}