/*
* Copyright (C) 2013-2017 NTT DATA Corporation
*
* 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.terasoluna.gfw.common.fullhalf;
import java.util.*;
/**
* Convert which converts from fullwidth to halfwidth and from halfwidth to fullwidth. This implementation does not have the
* mapping table and intended to be given by the constructor like following:
*
* <pre>
* <code>FullHalfConverter converter = new FullHalfConverter(new FullHalfPairsBuilder()
* .pair("A", "A")
* .pair("B", "B")
* .build());</code>
* </pre>
* <p>
* If the halfwidth or fullwidth in the given pair is already registered, the former is preferred. Note that it cannot be
* overridden.
* </p>
* @since 5.1.0
*/
public final class FullHalfConverter {
/**
* map to convert from fullwidth char
*/
private final Map<String, FullHalfPair> fullwidthMap;
/**
* map to convert from halfwidth char
*/
private final Map<String, FullHalfPair> halfwidthMap;
/**
* predicates if the given character is appendable like '゙' or '゚'.
*/
private final FullHalfPairs.AppendablePredicate predicate;
/**
* Constructor.
* @param pairs pair of fullwidth-halfwidth. must not be null. If the halfwidth or fullwidth in the given pair is already
* registered, the former is preferred.
* @see FullHalfConverter
* @throws IllegalArgumentException if the given pairs is null
*/
public FullHalfConverter(FullHalfPairs pairs) {
if (pairs == null) {
throw new IllegalArgumentException("pairs must not be null.");
}
Set<FullHalfPair> pairSet = pairs.pairs();
Map<String, FullHalfPair> f = new HashMap<String, FullHalfPair>();
Map<String, FullHalfPair> h = new HashMap<String, FullHalfPair>();
for (FullHalfPair pair : pairSet) {
// first definition is prior
if (!f.containsKey(pair.fullwidth())) {
f.put(pair.fullwidth(), pair);
}
if (!h.containsKey(pair.halfwidth())) {
h.put(pair.halfwidth(), pair);
}
}
this.fullwidthMap = Collections.unmodifiableMap(f);
this.halfwidthMap = Collections.unmodifiableMap(h);
this.predicate = pairs.predicate();
}
/**
* Converts from fullwidth to halfwidth as much as possible with the given mapping table.
* @param fullwidth string to convert
* @return converted string. if the given string is null or empty, returns as it is.
*/
public String toHalfwidth(String fullwidth) {
if (fullwidth == null || fullwidth.isEmpty()) {
return fullwidth;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < fullwidth.length(); i++) {
String s = String.valueOf(fullwidth.charAt(i));
builder.append(halfwidth(s));
}
return builder.toString();
}
/**
* Converts from halfwidth to fullwidth as much as possible with the given mapping table.
* @param halfwidth string to convert
* @return converted string. if the given string is null or empty, returns as it is.
*/
public String toFullwidth(String halfwidth) {
if (halfwidth == null || halfwidth.isEmpty()) {
return halfwidth;
}
StringBuilder builder = new StringBuilder();
Queue<String> buffer = new LinkedList<String>(); // use queue as 1 element buffer
for (int i = 0; i < halfwidth.length(); i++) {
char c = halfwidth.charAt(i);
String s = String.valueOf(c);
// next loop when the buffer is empty
if (buffer.isEmpty()) {
buffer.add(s);
continue;
}
// poll the previous string from buffer
String prev = buffer.poll();
// check if the target character is appendable
if (predicate.isAppendable(c)) {
// check if "previous string"+"appendable char" is contained in the mapping table
FullHalfPair pair = this.halfwidthMap.get(prev + s);
if (pair != null) {
// append the concatenated string
builder.append(pair.fullwidth());
} else {
// append fullwidth of the previous string and current string
builder.append(fullwidth(prev));
builder.append(fullwidth(s));
}
} else {
// append fullwidth of the previous string and put current string into the buffer
builder.append(fullwidth(prev));
buffer.add(s);
}
}
// append the string in the buffer if exists
if (!buffer.isEmpty()) {
builder.append(fullwidth(buffer.poll()));
}
return builder.toString();
}
/**
* Returns fullwidth string if the given halfwidth string exists in the pairs
* @param s halfwidth
* @return fullwidth for the given halfwidth
*/
private String fullwidth(String s) {
FullHalfPair pair = this.halfwidthMap.get(s);
if (pair != null) {
return pair.fullwidth();
} else {
return s;
}
}
/**
* Returns halfwidth string if the given fullwidth string exists in the pairs
* @param s fullwidth
* @return halfwidth for the given fullwidth
*/
private String halfwidth(String s) {
FullHalfPair pair = this.fullwidthMap.get(s);
if (pair != null) {
return pair.halfwidth();
} else {
return s;
}
}
}